优雅编程 - 组件扫描&拦截器

示例形式概述注解扫描,注解切面组件。

SpringBean注解扫描组件

Spring中bean注解扫描类ClassPathScanningCandidateComponentProvider, 该类构造参数如下:

public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) {
    this(useDefaultFilters, new StandardEnvironment());
}
public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters, Environment environment) {
    if (useDefaultFilters) {
        registerDefaultFilters();
    }
    this.environment = environment;
}

类实例化方式构造依赖, 参数意义:

useDefaultFilters: 是否走默认的springBean扫描策略, spring启动时该值默认是为true, 扫描的组件是@Component
environment: 环境变量相关,基于spring.profiles相关配置

扩展自定义类

自己扩展的注解需要被SpringBean注解扫描器扫到的话需要注解上增加@Component,

自定义注解示例

自定义注解,绑定Spring注解@Component

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Component
public @interface VClass {

    // 类名称
    public String name();

    // 表名称
    public String tableName();

    // 表注释
    public String comment() default "";
}

应用自定义注解

在自定义实体类上增加自定义注解

import com.wplus.plugin.htmlplus.anno.bean.VClass;

@VClass(name = "mock", tableName = "tb_mock", comment = "测试实体")
public class Mock {

    // this class used for test
}

使用Spring注解扫描器扫描自定义类

/**
* 根据路径扫描带有VClass注解的类
* @param scanPath 要扫描的包路径
* @return
*/
public List scanAnnoBeanName(String scanPath){
   if(StringUtils.isBlank(scanPath)){
       scanPath = DEFAULT_BEAN_PATH;
   }
   return scanAnnoBeanName(scanPath, VClass.class);
}


/**
* 根据路径扫描要用来映射的类
* @param scanPath 要扫描的包
* @return
*/
public List scanAnnoBeanName(String scanPath, Class annotationType){
   ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
   provider.addIncludeFilter(new AnnotationTypeFilter(annotationType));
   Set sets = provider.findCandidateComponents(scanPath);
   if(null !=sets && sets.size() > 0){
       List classNames = new ArrayList();
       for(BeanDefinition bean: sets){
           classNames.add(bean.getBeanClassName());
       }
       return classNames;
   }
   return null;
}

public static void main(String[] args) {
   List beans = new VanoBeanLoadApiImpl().scanAnnoBeanName("com.wplus.plugin");
   System.out.println(beans);
}

程序输出

[com.wplus.plugin.htmlplus.demo.Mock]

SpringResources资源扫描组件

Spring中Resources扫描类GenericApplicationContext, 对应的资源扫描方法如下:

public Resource getResource(String location) {
   return this.resourceLoader != null?this.resourceLoader.getResource(location):super.getResource(location);
}

Resources默认资源加载路径String CLASSPATH_URL_PREFIX = "classpath:";

SpringResources资源组件扩展

定义资源加载方法,增加资源后缀匹配过滤

public List scanResources(String locationPattern, final List accepts) {
   if(StringUtils.isBlank(locationPattern)){
       locationPattern = DEFAULT_TEMPLATE_PATH;
   }
   try {
       GenericApplicationContext context = new GenericApplicationContext();
       Resource rs[] = context.getResources(locationPattern);
       List list = (null != rs && rs.length > 0)? Arrays.asList(rs): new ArrayList();
       // do return resource list if accepts list is empty.
       if(CollectionUtils.isEmpty(accepts)){
           return list;
       }
       // filter resource which file extension in accepts
       List result = list.stream()  // convert list to stream
               .filter(line -> accepts.contains(".".concat(FilenameUtils.getExtension(line.getFilename()))))
               .collect(Collectors.toList());
       return result;
   }catch (Exception e){
       LOGGER.error(e.getMessage(), e);
   }
   return new ArrayList();
}

示例使用Spring资源加载组件,扫描指定路径下的资源模板, 第二个参数用来标识扫描资源结果匹配的过滤,
后缀在接收列表中的资源,会加载到结果集中.

调用示例模拟

public static void main(String[] args) {
   List list = new TemplateLoadApiImpl().scanResources("classpath:/HTemplate/**", Arrays.asList(".html"));
   list.forEach(li -> System.out.println(li.toString()));
}

方法通过扫描当前工程下资源模块HTemplate中的内容,递归遍历,收集后缀为.html结尾的资源文件.
如果要扫描jar包中的资源的话,classpath统配符应为"classpath*:/HTemplate/**"

@Aspect说明

Spring除了支持Schema方式配置AOP,还支持使用@Aspect注解方式切面声明。Spring默认注解支持关闭,开启配置:


这样Spring就能发现@AspectJ风格的切面并且将切面应用到目标对象。

@Aspect依赖包


    org.aspectj
    aspectjweaver
    1.8.9


    org.aspectj
    aspectjweaver
    1.8.9

@Aspect示例

定义切面,切入方法调用耗时

import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Arrays;
@Component
@Aspect
@Order(3)
public class TraceInvokeTimelineAroundAspect {

    private static final Logger LOG = LoggerFactory.getLogger(TraceInvokeTimelineAroundAspect.class);

    public final static long MONITOR_MAX_TIME = 5000;

    @Around(
            "pointRestful() || pointService() || pointMapper()"
    )
    public Object invoke(ProceedingJoinPoint point) throws Throwable {
        long startTime = System.currentTimeMillis();

        String classMethodName = point.getSignature().getDeclaringType().getSimpleName() + "." + point.getSignature().getName();
        Object result = point.proceed();

        long usedTime = System.currentTimeMillis() - startTime;
        StringBuffer buffer = new StringBuffer(600);
        buffer.append("耗时:").append(usedTime + "ms").append(" ");
        buffer.append("优化(>" + MONITOR_MAX_TIME + "ms):").append(usedTime >= MONITOR_MAX_TIME).append(" ");
        buffer.append("接口:").append(classMethodName).append(" ");
        buffer.append("入参:").append(Arrays.toString(point.getArgs())).append(" ");
        buffer.append("返回:").append(JSON.toJSONString(result));
        LOG.info(buffer.toString());
        return result;
    }

    /**
     * 声明切入点 - 控制层切入
     */
    @Pointcut("execution(* com.tutorial.aspectj.web.controller..*.*(..))")
    public void pointRestful() {

    }

    /**
     * 声明切入点 - 业务层切入
     */
    @Pointcut("execution(* com.tutorial.aspectj.facade.service..*.*(..))")
    public void pointService(){

    }

    /**
     * 声明切入点 - 数据层切入
     */
    @Pointcut("execution(* com.tutorial.aspectj.facade.mapper..*.*(..))")
    public void pointMapper() {

    }

开启@Aspect支持

示例中我们用注解@Component对自定的Aspect进行了实例化,需要保证注解所在包能被Spring实例注解扫描到。
要使切面在两个容器(spring&springmvc)中都生效,必须两个都必须配置开启注解支持.

  • Spring容器


         


  • SpringMvc容器


    
    


你可能感兴趣的:(优雅编程 - 组件扫描&拦截器)