电竞比分网-中国电竞赛事及体育赛事平台

分享

Spring自定義類掃描器

 muyable 2020-05-22
在我們剛開始接觸Spring的時候,要定義bean的話需要在xml中編寫,比如:
<bean id="myBean" class="your.pkg.YourClass"/>
后來發(fā)現(xiàn)如果bean比較多,會需要寫很多的bean標(biāo)簽,太麻煩了。于是出現(xiàn)了一個component-scan注解。這個注解直接指定包名就可以,它會去掃描這個包下所有的class,然后判斷是否解析:
<context:component-scan base-package="your.pkg"/>
再后來,由于注解Annotation的流行,出現(xiàn)了@ComponentScan注解,作用跟component-scan標(biāo)簽一樣,跟@Configuration注解配合使用:
  1. @ComponentScan(basePackages = {"your.pkg", "other.pkg"})
  2. public class Application { ... }
不論是component-scan標(biāo)簽,還是@ComponentScan注解。它們掃描或解析的bean只能是Spring內(nèi)部所定義的,比如@Component、@Service、@Controller或@Repository。如果有一些自定義的注解,比如@Consumer、這個注解修飾的類是不會被掃描到的。這個時候我們就得自定義掃描器完成這個操作。
 
Spring內(nèi)置的掃描器
 
component-scan標(biāo)簽底層使用ClassPathBeanDefinitionScanner這個類完成掃描工作的。@ComponentScan注解配合@Configuration注解使用,底層使用ComponentScanAnnotationParser解析器完成解析工作。

ComponentScanAnnotationParser解析器內(nèi)部使用了ClassPathBeanDefinitionScanner掃描器,ClassPathBeanDefinitionScanner掃描器內(nèi)部的處理過程整理如下:

1. 遍歷basePackages,根據(jù)每個basePackage找出這個包下的所有的class。比如basePackage為your/pkg,會找出your.pkg包下所有的class。找出之后封裝成Resource接口集合,這個Resource接口是Spring對資源的封裝,有FileSystemResource、ClassPathResource、UrlResource實現(xiàn)等
2. 遍歷找到的Resource集合,通過includeFilters和excludeFilters判斷是否解析。這里的includeFilters和excludeFilters是TypeFilter接口類型的集合,是ClassPathBeanDefinitionScanner內(nèi)部的屬性。TypeFilter接口是一個用于判斷類型是否滿足要求的類型過濾器。excludeFilters中只要有一個TypeFilter滿足條件,這個Resource就會被過濾。includeFilters中只要有一個TypeFilter滿足條件,這個Resource就不會被過濾
3. 如果沒有被過濾。把Resource封裝成ScannedGenericBeanDefinition添加到BeanDefinition結(jié)果集中
4. 返回最后的BeanDefinition結(jié)果集
 
TypeFilter接口的定義:
 
  1. public interface TypeFilter {
  2. boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
  3. throws IOException;
  4. }
TypeFilter接口目前有AnnotationTypeFilter實現(xiàn)類(類是否有注解修飾)、RegexPatternTypeFilter(類名是否滿足正則表達(dá)式)等。

ClassPathBeanDefinitionScanner繼承ClassPathScanningCandidateComponentProvider類。

ClassPathScanningCandidateComponentProvider內(nèi)部的構(gòu)造函數(shù)提供了一個useDefaultFilters參數(shù):
 
  1. public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) {
  2. this(useDefaultFilters, new StandardEnvironment());
  3. }
useDefaultFilters這個參數(shù)表示是否使用默認(rèn)的TypeFilter,如果設(shè)置為true,會添加默認(rèn)的TypeFilter:
  1. protected void registerDefaultFilters() {
  2. this.includeFilters.add(new AnnotationTypeFilter(Component.class));
  3. ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
  4. try {
  5. this.includeFilters.add(new AnnotationTypeFilter(
  6. ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
  7. logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
  8. }
  9. catch (ClassNotFoundException ex) {
  10. // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
  11. }
  12. try {
  13. this.includeFilters.add(new AnnotationTypeFilter(
  14. ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
  15. logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
  16. }
  17. catch (ClassNotFoundException ex) {
  18. // JSR-330 API not available - simply skip.
  19. }
  20. }
我們看到這里includeFilters加上了AnnotationTypeFilter,并且對應(yīng)的注解是@Component。@Service、@Controller或@Repository注解它們內(nèi)部都是被@Component注解所修飾的,所以它們也會被識別。
 
自定義掃描功能
 
一般情況下,我們要自定義掃描功能的話,可以直接使用ClassPathScanningCandidateComponentProvider完成,加上一些自定義的TypeFilter即可?;蛘邔憘€自定義掃描器繼承ClassPathScanningCandidateComponentProvider,并在內(nèi)部添加自定義的TypeFilter。后者相當(dāng)于對前者的封裝。

我們就以一個簡單的例子說明一下自定義掃描的實現(xiàn),直接使用ClassPathScanningCandidateComponentProvider。

項目結(jié)構(gòu)如下:
  1. ./
  2. └── spring
  3. └── study
  4. └── componentprovider
  5. ├── annotation
  6. │   └── Consumer.java
  7. ├── bean
  8. │   ├── ConsumerWithComponentAnnotation.java
  9. │   ├── ConsumerWithConsumerAnnotation.java
  10. │   ├── ConsumerWithInterface.java
  11. │   ├── ConsumerWithNothing.java
  12. │   └── ProducerWithInterface.java
  13. └── interfaze
  14.    ├── IConsumer.java
  15.    └── IProducer.java
我們直接使用ClassPathScanningCandidateComponentProvider掃描spring.study.componentprovider.bean包下的class:
  1. ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); // 不使用默認(rèn)的TypeFilter
  2. provider.addIncludeFilter(new AnnotationTypeFilter(Consumer.class));
  3. provider.addIncludeFilter(new AssignableTypeFilter(IConsumer.class));
  4. Set<BeanDefinition> beanDefinitionSet = provider.findCandidateComponents("spring.study.componentprovider.bean");
這里掃描出來的類只有2個,分別是ConsumerWithConsumerAnnotation(被@Consumer注解修飾)和ConsumerWithInterface(實現(xiàn)了IConsumer接口)。ConsumerWithComponentAnnotation使用@Component注解,ConsumerWithNothing沒實現(xiàn)任何借口,沒使用任何注解,ProducerWithInterface實現(xiàn)了IProducer接口;所以這3個類不會被識別。

如果我們要自定義ComponentProvider,繼承ClassPathScanningCandidateComponentProvider類即可。

RepositoryComponentProvider這個類是SpringData模塊提供的,繼承自ClassPathScanningCandidateComponentProvider,主要是為了識別SpringData相關(guān)的類。

它內(nèi)部定義了一些自定義TypeFilter,比如InterfaceTypeFilter(識別接口的TypeFilter,目標(biāo)比較是個接口,而不是實現(xiàn)類)、AllTypeFilter(保存存儲TypeList集合,這個集合內(nèi)部所有的TypeFilter必須全部滿足條件才能被識別)等。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多