在我們剛開始接觸Spring的時(shí)候锭魔,要定義bean的話需要在xml中編寫例证,比如:
<bean id="myBean" class="your.pkg"/>
后來發(fā)現(xiàn)如果bean比較多赂毯,會(huì)需要寫很多的bean標(biāo)簽战虏,太麻煩了党涕。于是出現(xiàn)了一個(gè)component-scan注解。這個(gè)注解直接指定包名就可以巡社,它會(huì)去掃描這個(gè)包下所有的class膛堤,然后判斷是否解析:
<context:component-scan base-package="your.pkg"/>
再后來,由于注解Annotation的流行晌该,出現(xiàn)了@ComponentScan注解,作用跟component-scan標(biāo)簽一樣朝群,跟@Configuration注解配合使用:
@ComponentScan(basePackages = {"your.pkg", "other.pkg"})
public class Application { ... }
不論是component-scan標(biāo)簽,還是@ComponentScan注解誉帅。它們掃描或解析的bean只能是Spring內(nèi)部所定義的,比如@Component蚜锨、@Service、@Controller或@Repository亚再。如果有一些自定義的注解,比如@Consumer晨抡、這個(gè)注解修飾的類是不會(huì)被掃描到的。這個(gè)時(shí)候我們就得自定義掃描器完成這個(gè)操作如捅。
Spring內(nèi)置的掃描器
component-scan標(biāo)簽底層使用ClassPathBeanDefinitionScanner這個(gè)類完成掃描工作的。@ComponentScan注解配合@Configuration注解使用伪朽,底層使用ComponentScanAnnotationParser解析器完成解析工作。
ComponentScanAnnotationParser解析器內(nèi)部使用了ClassPathBeanDefinitionScanner掃描器烈涮,ClassPathBeanDefinitionScanner掃描器內(nèi)部的處理過程整理如下:
- 遍歷basePackages,根據(jù)每個(gè)basePackage找出這個(gè)包下的所有的class坚洽。比如basePackage為your/pkg,會(huì)找出your.pkg包下所有的class讶舰。找出之后封裝成Resource接口集合,這個(gè)Resource接口是Spring對(duì)資源的封裝跳昼,有FileSystemResource、ClassPathResource鹅颊、UrlResource實(shí)現(xiàn)等
- 遍歷找到的Resource集合,通過includeFilters和excludeFilters判斷是否解析堪伍。這里的includeFilters和excludeFilters是TypeFilter接口類型的集合,是ClassPathBeanDefinitionScanner內(nèi)部的屬性帝雇。TypeFilter接口是一個(gè)用于判斷類型是否滿足要求的類型過濾器涮俄。excludeFilters中只要有一個(gè)TypeFilter滿足條件,這個(gè)Resource就會(huì)被過濾尸闸。includeFilters中只要有一個(gè)TypeFilter滿足條件彻亲,這個(gè)Resource就不會(huì)被過濾
- 如果沒有被過濾。把Resource封裝成ScannedGenericBeanDefinition添加到BeanDefinition結(jié)果集中
- 返回最后的BeanDefinition結(jié)果集
TypeFilter接口的定義:
public interface TypeFilter {
boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException;
}
TypeFilter接口目前有AnnotationTypeFilter實(shí)現(xiàn)類(類是否有注解修飾)室叉、RegexPatternTypeFilter(類名是否滿足正則表達(dá)式)等睹栖。
ClassPathBeanDefinitionScanner繼承ClassPathScanningCandidateComponentProvider類。
ClassPathScanningCandidateComponentProvider內(nèi)部的構(gòu)造函數(shù)提供了一個(gè)useDefaultFilters參數(shù):
public ClassPathScanningCandidateComponentProvider(boolean useDefaultFilters) {
this(useDefaultFilters, new StandardEnvironment());
}
useDefaultFilters這個(gè)參數(shù)表示是否使用默認(rèn)的TypeFilter茧痕,如果設(shè)置為true野来,會(huì)添加默認(rèn)的TypeFilter:
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
我們看到這里includeFilters加上了AnnotationTypeFilter,并且對(duì)應(yīng)的注解是@Component曼氛。@Service、@Controller或@Repository注解它們內(nèi)部都是被@Component注解所修飾的舀患,所以它們也會(huì)被識(shí)別气破。
自定義掃描功能
一般情況下,我們要自定義掃描功能的話,可以直接使用ClassPathScanningCandidateComponentProvider完成低匙,加上一些自定義的TypeFilter即可∑劭梗或者寫個(gè)自定義掃描器繼承ClassPathScanningCandidateComponentProvider强重,并在內(nèi)部添加自定義的TypeFilter绞呈。后者相當(dāng)于對(duì)前者的封裝间景。
我們就以一個(gè)簡單的例子說明一下自定義掃描的實(shí)現(xiàn),直接使用ClassPathScanningCandidateComponentProvider秉溉。
項(xiàng)目結(jié)構(gòu)如下:
./
└── 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:
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); // 不使用默認(rèn)的TypeFilter
provider.addIncludeFilter(new AnnotationTypeFilter(Consumer.class));
provider.addIncludeFilter(new AssignableTypeFilter(IConsumer.class));
Set<BeanDefinition> beanDefinitionSet = provider.findCandidateComponents("spring.study.componentprovider.bean");
這里掃描出來的類只有2個(gè)碗誉,分別是ConsumerWithConsumerAnnotation(被@Consumer注解修飾)和ConsumerWithInterface(實(shí)現(xiàn)了IConsumer接口)父晶。ConsumerWithComponentAnnotation使用@Component注解,ConsumerWithNothing沒實(shí)現(xiàn)任何借口甲喝,沒使用任何注解,ProducerWithInterface實(shí)現(xiàn)了IProducer接口埠胖;所以這3個(gè)類不會(huì)被識(shí)別。
如果我們要自定義ComponentProvider非竿,繼承ClassPathScanningCandidateComponentProvider類即可谋竖。
RepositoryComponentProvider這個(gè)類是SpringData模塊提供的红柱,繼承自ClassPathScanningCandidateComponentProvider蓖乘,主要是為了識(shí)別SpringData相關(guān)的類。
它內(nèi)部定義了一些自定義TypeFilter零聚,比如InterfaceTypeFilter(識(shí)別接口的TypeFilter,目標(biāo)比較是個(gè)接口隶症,而不是實(shí)現(xiàn)類)、AllTypeFilter(保存存儲(chǔ)TypeList集合沿腰,這個(gè)集合內(nèi)部所有的TypeFilter必須全部滿足條件才能被識(shí)別)等。
有興趣的讀者可以查看源碼颂龙。