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

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

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<String> scanAnnoBeanName(String scanPath){
   if(StringUtils.isBlank(scanPath)){
       scanPath = DEFAULT_BEAN_PATH;
   }
   return scanAnnoBeanName(scanPath, VClass.class);
}


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

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

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

调用示例模拟

public static void main(String[] args) {
   List<Resource> 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默认注解支持关闭,开启配置:

<aop:aspectj-autoproxy/>

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

@Aspect依赖包

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
</dependency>

@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)中都生效,必须两个都必须配置<aop:aspectj-autoproxy/>开启注解支持.

  • Spring容器
<context:annotation-config/>
<context:component-scan base-package="com.tutorial.aspectj..**" use-default-filters="true">
         <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<aop:aspectj-autoproxy/>
  • SpringMvc容器
<context:annotation-config/>
<context:component-scan base-package="com.tutorial.aspectj.web.controller..**" use-default-filters="true">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
<aop:aspectj-autoproxy/>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 概述 Spring是什么? Spring是一个开源框架,为了解决企业应用开发的复杂性而创建的,但是现在已经不止于企...
    琅筑阅读 1,158评论 2 8
  • spring官方文档:http://docs.spring.io/spring/docs/current/spri...
    牛马风情阅读 1,650评论 0 3
  • 本文我们来梳理一下Spring的那些注解,如下图所示,大概从几方面列出了Spring的一些注解: 如果此图看不清楚...
    Java自闭师阅读 727评论 0 1
  • 1.什么是spring? Spring是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,...
    青玉_f18c阅读 328评论 0 2
  • 今天早早吃完饭去学校,参加期中考试后的家长会,家长会的主题是:《家校合作,培养学生良好的品行》。 董老师说...
    泉霖妈阅读 107评论 0 1