Spring @ComponentScan注解用法和原理解析

1.概述

Spring是如何通过注解的形式将Bean注入到Spring容器当中的呢?

答案就在@ComponentScan注解上,该注解告诉Spring要去哪里去寻找Bean。

通过这篇博客将学习到如何正确使用@ComponentScan,并分析其底层是如何实现。

2.@ComponentScan

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)//可重复注解
public @interface ComponentScan {
    
    

   @AliasFor("basePackages")
   String[] value() default {
    
    };//基础包名,等同于basePackages

   @AliasFor("value")
   String[] basePackages() default {
    
    };//基础包名,value

   Class<?>[] basePackageClasses() default {
    
    };//扫描的类,会扫描该类所在包及其子包的组件。

   Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;//注册为BeanName生成策略 默认BeanNameGenerator,用于给扫描到的Bean生成BeanName

   Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;//用于解析bean的scope的属性的解析器,默认是AnnotationScopeMetadataResolver

   ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;//scoped-proxy 用来配置代理方式 // no(默认值):如果有接口就使用JDK代理,如果没有接口就使用CGLib代理 interfaces: 接口代理(JDK代理) targetClass:类代理(CGLib代理)

   String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;//配置要扫描的资源的正则表达式的,默认是"**/*.class",即配置类包下的所有class文件。
  
   boolean useDefaultFilters() default true;//useDefaultFilters默认是true,扫描带有@Component ro @Repository ro @Service ro @Controller 的组件

   Filter[] includeFilters() default {
    
    };//包含过滤器

   Filter[] excludeFilters() default {
    
    };//排除过滤器

   boolean lazyInit() default false;//是否是懒加载

   @Retention(RetentionPolicy.RUNTIME)
   @Target({
    
    })
   @interface Filter {
    
    //过滤器注解

      FilterType type() default FilterType.ANNOTATION;//过滤判断类型

      @AliasFor("classes")
      Class<?>[] value() default {
    
    };//要过滤的类,等同于classes

      @AliasFor("value")
      Class<?>[] classes() default {
    
    };//要过滤的类,等同于value

      String[] pattern() default {
    
    };// 正则化匹配过滤

   }

}

该注解是一个@Repeatable注解,表明是可以重复使用的,例如
在这里插入图片描述
或者使用@ComponentScans注解,该注解仅有一个属性就是@ComponentScan注解的数组,用法如下:在这里插入图片描述
然而当我debug调试的时候,竟然发现上面这样的做法是无法扫描指定包下的类的。

这是为什么呢?

Spring启动的时候,会执行ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法,其中就会遍历已有的BeanDefinitionNames,此时appConfig就是其中之一,会判断它是否是一个配置类
在这里插入图片描述
如果是配置类的话,则会加入到configCandidates集合当中。

判断的方法是ConfigurationClassUtils.checkConfigurationClassCandidate
在这里插入图片描述
此时没有加@Configuration注解,所以config为null,那么就会判断是否是一个半配置类。

调用isConfigurationCandidate方法

半配置类的标准是是否有这4个注解中的一种
在这里插入图片描述
当使用2个及以上的@ComponentScan注解的时候,或者使用@ComponentScans注解的时候,此时的metadata如下:
在这里插入图片描述
注解的类型不是这4个其中一个。

最后就是判断是否有注解@Bean的注解方法。很显然此时的AppConfig并没有,所以返回false。说明它不是一个配置类。

那么configCandidates则为空,直接返回,不会进行之后的扫描逻辑了。
在这里插入图片描述
所以当使用2个及以上的@ComponentScan注解,或者使用@ComponentScans的使用需要再加上@Configuration注解表明他是一个配置类,这样才会进行下面的扫描逻辑。

其中的属性使用都比较简单,相对复杂一点的是includeFilters和excludeFilters,下面举例说明一下。

包结构如下所示:
在这里插入图片描述
其中A,B,C,D类的代码如下:

@Controller
public class A {
    
    
}
@Component
public class B {
    
    
}
@Service
public class C {
    
    
}
@Repository
public class D {
    
    
}

启动类如下:

@ComponentScan(basePackages = "hdu.gongsenlin6")
public class AppConfig {
    
    
   public static void main(String[] args) throws IOException {
    
    
      AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
      System.in.read();
   }

使用默认的过滤器,扫描hdu.gongsenlin6包下的类,此时容器中内容如下所示:
在这里插入图片描述
4个都被扫描到了,将启动类的代码更改一下,使用excludeFilters和includeFilters。

注意此时useDefaultFilters设置为false,不使用它默认的。
在这里插入图片描述
excludeFilters排除拥有注解@Service的类,includeFilters包含拥有注解@Repository或者注解@Controller的类。此时容器的内容如下:
在这里插入图片描述
只有a和d被注入到Spring容器当中了。

这两个过滤器的使用还是比较简单的,一是指定过滤的类型FilterType,然后根据类型来指定过滤的条件value。

FilterType过滤类型有如下几种:
在这里插入图片描述
最后一种自定义过滤器,要实现TypeFilter接口,编写match方法。
在这里插入图片描述

3.源码分析

这一小节将分析源码,探究根据@ComponentScan进行扫描的底层是如何实现的。

在上一节中,ConfigurationClassPostProcessor中的processConfigBeanDefinitions方法,第一步是找到配置类,也就是找到了AppConfig这个类。

然后会构造一个配置类解析器ConfigurationClassParser,来对AppConfig进行解析。
在这里插入图片描述
下面重点来看看parse方法是如何解析AppConfig。
在这里插入图片描述
遍历配置类候选集合,此时只有AppConfig。

拿到bean的定义,判断该bean属于AnnotatedBeanDefinition。

所以会进入第一个if的parse方法。
在这里插入图片描述
根据注解元数据和beanName封装成ConfigurationClass,调用processConfigurationClass
在这里插入图片描述
第一个if,判断是否跳过的条件,也就是condition相关的信息,@Conditional注解,在之后的博客再做分析。

此时明显是是没有加条件的,所以这里不会跳过。之后判断缓存中是否已有当前准备解析的配置类,显然也是没有的,只有在处理了之后,才会放入该缓存当中。

调用asSourceClass,递归地处理配置类及其超类层次结构,得到类元信息,也就是这个类的相关信息都在sourceClass当中,sourceClass如下:
在这里插入图片描述
之后调用doProcessConfigurationClass方法,do开头的方法往往都是真正做大事的方法。

该方法很长,大致意思就是根据不同的注解的类型,来调用不同的处理逻辑。

本文关注点放在@ComponentScan注解上,我们来分析红色框框的部分。
在这里插入图片描述
调用AnnotationConfigUtils的静态方法attributesForRepeatable,获取@ComponentScan注解的属性。

得到的结果如下:
在这里插入图片描述
之后就是一个for循环,遍历componentScans,此时仅有一个componentScan,使用componentScanParser解析器来解析componentScan这个对象。

调用componentScanParser的parse方法。
在这里插入图片描述
虽然很长,但是逻辑还是比较简单的,初始化ClassPathBeanDefinitionScanner扫描器

根据componentScan的属性,设置扫描器的属性。

最后调用扫描器的doScan方法执行真正的扫描工作。
在这里插入图片描述
遍历扫描包,调用findCandidateComponents方法根据基础包路径来找到候选的Bean。

之后就是遍历扫描到的候选Bean,给他们设置作用域,生成BeanName等一系列的操作。然后检查BeanName是否冲突,添加到beanDefinitions集合当中,调用registerBeanDefinition注册Bean,将Bean的定义注册到Spring容器当中。

完成扫描,方法返回到上面红色框框的部分。
在这里插入图片描述
遍历扫描到的所有Bean,判断是否是配置类,如果是的话,那么就会递归的调用parse去解析它。完结撒花~

猜你喜欢

转载自blog.csdn.net/gongsenlin341/article/details/114291296