上一篇咱們說到doProcessConfigurationClass方法來進行處理,在該方法中會針對@Component、@PropertySource、@ComponentScans、@ComponentScan、@Import、@ImportResource、@Bean注解的配置類分别處理,這一篇幅咱們詳細分析doProcessConfigurationClass方法的具體邏輯,先看代碼:
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
//關鍵點一
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);
}
// 關鍵點二
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
//關鍵點三
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// Process any @Import annotations
// 關鍵點四
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Process any @ImportResource annotations
// 關鍵點五
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// Process individual @Bean methods
// 關鍵點六
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
// 關鍵點七
processInterfaces(configClass, sourceClass);
// Process superclass, if any
// 關鍵點八
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
return sourceClass.getSuperClass();
}
}
return null;
}
關鍵點一
針對有@Component注解的,調用processMemberClasses進行處理,該方法中擷取内部類, 檢視内部類上是否存在Component、ComponentScan、Import、ImportResource注解,如果存在則調用processConfigurationClass方法進行處理,說白了就内部内也是需要處理的。
關鍵點二
解析@PropertySource注解,這裡Spring會去解析注解所指定的properties檔案,并添加到environment中去。
關鍵點三
處理@ComponentScans、@ComponentScan注解,針對這兩個注解,Spring會調用this.componentScanParser.parse方法掃描注解中指定目錄下的所有類,得到BeanDefinition集合,然後調用checkConfigurationClassCandidate方法檢查掃描所得到BeanDefinition是不是配置Bean。這裡我們對componentScanParser.parse詳細介紹下:
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
Set<String> basePackages = new LinkedHashSet<>();
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
這裡主要注意includeFilters和excludeFilters兩個主要是幹嘛的,就Spring而言哪些類的對象需要被Spring容器管理呢?一種是像@Component這些Spring定義的注解,除此之外Spring還允許咱們自己定義哪些注解類是需要被管理的,哪些注解類是一定不會被管理的,我們在注解ComponentScan上指定includeFilters用于指定需要被管理的,excludeFilters指定需要被排除的。
而basePackage和basePackages這兩個主要是指定要被掃描的類所在的路徑,調用scanner.doScan開始掃描(在該方法中又會調用findCandidateComponents方法去每個包路徑下去找)。掃描這個詞可能不好了解,其實就是循環去讀取包下面所有類的元資訊,啥是元資訊?比如類的全路徑名、有哪些注解等等構成了元資訊。當讀取到資訊,并且判斷isCandidateComponent後,會生成ScannedGenericBeanDefinition類型的BeanDefinition,後面我們單獨會講BeanDefinition的結構。掃描得到BeanDefinition後,該BeanDefinition會被添加到BeanFactory這個Bean工廠的beanDefinitionMap集合中。
關鍵點四
解析@Import,在使用@Import注解時,我們需要指定類,Spring對于這些指定的類有三種處理方式,下面來看代碼:
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
......
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}else {
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
......
}
注意這個方法的第三個參數importCandidates,這個是getImports方法的傳回值,是Import的參數值對應的類的元資訊。然後我們看上面的for循環,它是循環處理元資訊集合資料,這裡有三種處理方式:
1.如果import指定的類是一個ImportSelector的實作類,則會調用它的selectImports方法,該方法會傳回一個String數組,指定多個配置類。
2.如果import指定的類是一個ImportBeanDefinitionRegistrar的實作類,就把該類的執行個體放入importBeanDefinitionRegistrars中,後面再執行該執行個體的registerBeanDefinitions方法。
3.如果是一個普通的類,則當作普通的配置類處理。
以上是我們經常使用Import注解的場景,大家不防好好會議下是不是!
關鍵點五
解析@ImportResource,得到導入進來的spring的xml配置檔案,然後解析。這裡隻是添加到配置類的importedResources集合中。
關鍵點六
解析配置類中的加了@Bean注解的方法。這裡隻是添加到了配置類的beanMethods集合中,後續會進行處理。
關鍵點七
如果配置類實作了某個接口,那麼還會解析該接口中的加了@Bean注解的預設方法。
關鍵點八
如果有父類,則傳回父類給上層周遊進行處理。