在我們配置的Spring-ApplicationContext文件中,context:component-scan是一個很重要的節點,它主要能幫助我們掃描指定包下面,需要加載到beanFactory容器的類,現在我們就來鑽研一下這個掃描和加載的過程。
前面需要注意的地方在http://singleant.iteye.com/blog/1177358中已經說得很清楚了,現在我們只討論當文件解析器遇到context:component-scan時的操作過程。
component-scan主要分爲以下幾步:
1.根據context獲取到相應的namespaceUri,即http://www.springframework.org/schema/context
2.根據namespaceUri獲取到相應的NamespaceHandler或者是className,如果是className,則還需要利用反射進行實例化,得到的是ContextNamespaceHandler,
3.再根據component-scan獲取相應的parser,ComponentScanBeanDefinitionParser
public BeanDefinition parse(Element element, ParserContext parserContext) {
return findParserForElement(element, parserContext).parse(element, parserContext);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
4.根據base-package帶進來的參數,分割成數組
String[] basePackages = StringUtils.tokenizeToStringArray(element.getAttribute(BASE_PACKAGE_ATTRIBUTE),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
其中ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS = ",; \t\n";
因此在base-package中的不同包可以用逗號,分號,製表符和換行符分割開,甚至可以混合使用。
5.然後就是新建scanner
protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
XmlReaderContext readerContext = parserContext.getReaderContext();
boolean useDefaultFilters = true;
if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
useDefaultFilters = Boolean.valueOf(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
}
// Delegate bean definition registration to scanner class.
ClassPathBeanDefinitionScanner scanner = createScanner(readerContext, useDefaultFilters);
scanner.setResourceLoader(readerContext.getResourceLoader());
scanner.setEnvironment(parserContext.getDelegate().getEnvironment());
scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
}
try {
parseBeanNameGenerator(element, scanner);
}
catch (Exception ex) {
readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
}
try {
parseScope(element, scanner);
}
catch (Exception ex) {
readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
}
parseTypeFilters(element, scanner, readerContext, parserContext);
return scanner;
}
component-scan一共有三個attribute可以配置annotation-config,use-default-filters還有base-package,其中use-default-filters的默認值是true,這樣會有三個includeFilter和0個excludeFilter添加,這個後面再詳說。
6.然後就可以真正的scanner.doScan(basePackages),進入搜索環節
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
}
//這裏因代碼太多,刪掉了一些log代碼和異常捕捉代碼
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
//此時傳入的 basePackage="com.lh708.controller"
Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
try {
//將包名轉化爲文件夾名 classpath*:com/lh708/controller/**/*.class
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + "/" + this.resourcePattern;
//這一步會現將classpath*:com/lh708/controller/**/*.class轉換爲本地文件夾路徑,然後搜索文件夾下所有已.class結尾的文件,並封裝到Resource返回
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (resource.isReadable()) {
try {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) { //NOTE 1
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) { //NOTE 2
candidates.add(sbd);
}
}
}
}
}
}
return candidates;
}
NOTE 1: 這裏會進行Filter檢驗,上文提到的use-default-filters就與此相關,沒有配置的話,默認是true,只會裝在三個includeFilters,即三個AnnotationTypeFilter:Component,ManagedBean以及Named
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) { //NOTE 3
return isConditionMatch(metadataReader); //當次類有被Conditional修飾時,並且滿足忽略條件時,也會返回FALSE
}
}
return false;
}
NOTE 2:這裏會進行抽象,接口和內部類檢查
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return (beanDefinition.getMetadata().isConcrete() && beanDefinition.getMetadata().isIndependent());
}
@Override
public boolean isConcrete() {
//不可爲接口或抽象類
return !(this.isInterface || this.isAbstract);
}
@Override
public boolean isIndependent() {
//不可爲內部類
return (this.enclosingClassName == null || this.independentInnerClass);
}
NOTE 3:以Component爲例,當這個類有被Component或者其衍生註解如Controller,Service,Repository修飾,返回true
protected boolean matchSelf(MetadataReader metadataReader) {
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
return metadata.hasAnnotation(this.annotationType.getName()) ||
(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
}
綜上所述,能被檢測並且正常添加到candidates應該滿足幾個條件
1.在被搜索的包下面
2.不是接口或抽象類,也不是內部類
3.不符合excludeFilters的條件,滿足includeFilters的條件
4.被Conditional修飾時,需要看條件決定