在我们刚开始接触Spring的时候,要定义bean的话需要在xml中编写,比如:
1 | <bean id="myBean" class="your.pkg.YourClass"/> |
后来发现如果bean比较多,会需要写很多的bean标签,太麻烦了。于是出现了一个component-scan注解。这个注解直接指定包名就可以,它会去扫描这个包下所有的class,然后判断是否解析:
1 | <context:component-scan base-package="your.pkg"/> |
再后来,由于注解Annotation的流行,出现了@ComponentScan注解,作用跟component-scan标签一样,跟@Configuration注解配合使用:
1 | "your.pkg", "other.pkg"}) (basePackages = { |
不论是component-scan标签,还是@ComponentScan注解。它们扫描或解析的bean只能是Spring内部所定义的,比如@Component、@Service、@Controller或@Repository。如果有一些自定义的注解,比如@Consumer、这个注解修饰的类是不会被扫描到的。这个时候我们就得自定义扫描器完成这个操作。
Spring内置的扫描器
component-scan标签底层使用ClassPathBeanDefinitionScanner这个类完成扫描工作的。@ComponentScan注解配合@Configuration注解使用,底层使用ComponentScanAnnotationParser解析器完成解析工作。
ComponentScanAnnotationParser解析器内部使用了ClassPathBeanDefinitionScanner扫描器,ClassPathBeanDefinitionScanner扫描器内部的处理过程整理如下:
- 遍历basePackages,根据每个basePackage找出这个包下的所有的class。比如basePackage为your/pkg,会找出your.pkg包下所有的class。找出之后封装成Resource接口集合,这个Resource接口是Spring对资源的封装,有FileSystemResource、ClassPathResource、UrlResource实现等
- 遍历找到的Resource集合,通过includeFilters和excludeFilters判断是否解析。这里的includeFilters和excludeFilters是TypeFilter接口类型的集合,是ClassPathBeanDefinitionScanner内部的属性。TypeFilter接口是一个用于判断类型是否满足要求的类型过滤器。excludeFilters中只要有一个TypeFilter满足条件,这个Resource就会被过滤。includeFilters中只要有一个TypeFilter满足条件,这个Resource就不会被过滤
- 如果没有被过滤。把Resource封装成ScannedGenericBeanDefinition添加到BeanDefinition结果集中
- 返回最后的BeanDefinition结果集
TypeFilter接口的定义:
1 | public interface TypeFilter { |
TypeFilter接口目前有AnnotationTypeFilter实现类(类是否有注解修饰)、RegexPatternTypeFilter(类名是否满足正则表达式)等。
ClassPathBeanDefinitionScanner继承ClassPathScanningCandidateComponentProvider类。
ClassPathScanningCandidateComponentProvider内部的构造函数提供了一个useDefaultFilters参数:
1 | public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) { |
useDefaultFilters这个参数表示是否使用默认的TypeFilter,如果设置为true,会添加默认的TypeFilter:
1 | protected void registerDefaultFilters() { |
我们看到这里includeFilters加上了AnnotationTypeFilter,并且对应的注解是@Component。@Service、@Controller或@Repository注解它们内部都是被@Component注解所修饰的,所以它们也会被识别。
自定义扫描功能
一般情况下,我们要自定义扫描功能的话,可以直接使用ClassPathScanningCandidateComponentProvider完成,加上一些自定义的TypeFilter即可。或者写个自定义扫描器继承ClassPathScanningCandidateComponentProvider,并在内部添加自定义的TypeFilter。后者相当于对前者的封装。
我们就以一个简单的例子说明一下自定义扫描的实现,直接使用ClassPathScanningCandidateComponentProvider。
项目结构如下:
./
└── spring
└── study
└── componentprovider
├── annotation
│ └── Consumer.java
├── bean
│ ├── ConsumerWithComponentAnnotation.java
│ ├── ConsumerWithConsumerAnnotation.java
│ ├── ConsumerWithInterface.java
│ ├── ConsumerWithNothing.java
│ └── ProducerWithInterface.java
└── interfaze
├── IConsumer.java
└── IProducer.java
我们直接使用ClassPathScanningCandidateComponentProvider扫描spring.study.componentprovider.bean包下的class:
1 | ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); // 不使用默认的TypeFilter |
这里扫描出来的类只有2个,分别是ConsumerWithConsumerAnnotation(被@Consumer注解修饰)和ConsumerWithInterface(实现了IConsumer接口)。ConsumerWithComponentAnnotation使用@Component注解,ConsumerWithNothing没实现任何借口,没使用任何注解,ProducerWithInterface实现了IProducer接口;所以这3个类不会被识别。
如果我们要自定义ComponentProvider,继承ClassPathScanningCandidateComponentProvider类即可。
RepositoryComponentProvider这个类是SpringData模块提供的,继承自ClassPathScanningCandidateComponentProvider,主要是为了识别SpringData相关的类。
它内部定义了一些自定义TypeFilter,比如InterfaceTypeFilter(识别接口的TypeFilter,目标比较是个接口,而不是实现类)、AllTypeFilter(保存存储TypeList集合,这个集合内部所有的TypeFilter必须全部满足条件才能被识别)等。
有兴趣的读者可以查看源码。