基于 Spring Framework v5.2.6.RELEASE
接上篇:Spring 源码阅读 36:postProcessBeanDefinitionRegistry 对 @Configuration 配置的解析和处理(1)
概述
这一篇接着上篇,继续分析 ConfigurationClassPostProcessor 中postProcessBeanDefinitionRegistry
方法对标记了@
Configuration
注解的类的解析和处理。
建议两篇一起阅读。
处理过程
下图是postProcessBeanDefinitionRegistry
方法中执行具体解析过程的processConfigBeanDefinitions
方法的源代码。
上一篇分析了前半部分,也就是do-while
循环之前的部分。Spring 通过对所有注册到容器中的 BeanDefinition 进行筛选,找到了所有符合候选条件的配置类对应的 BeanDefinition,并且进行了一些准备性的处理。
接下来在do-while
循环中,对这些符合候选条件的 BeanDefinition,也就是candidates
集合中的元素,进行处理。下面开始。
配置类的解析
进入do-while
语句块之后,第一步就是使用实现创建好的配置类解析器对candidates
集合进行解析。
parser.parse(candidates);
我们进入这个方法查询解析的过程。
// org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)publicvoidparse(Set<BeanDefinitionHolder>configCandidates) { for (BeanDefinitionHolderholder : configCandidates) { BeanDefinitionbd=holder.getBeanDefinition(); try { if (bdinstanceofAnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } elseif (bdinstanceofAbstractBeanDefinition&& ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreExceptionex) { throwex; } catch (Throwableex) { thrownewBeanDefinitionStoreException( "Failed to parse configuration class ["+bd.getBeanClassName() +"]", ex); } } this.deferredImportSelectorHandler.process(); }
针对candidates
集合中的每一个 BeanDefinition,这里会根据它具体的类型,以 BeanDefinition 的元数据和 Bean 名称为参数,执行对应的parse
重载方法,因为我们要处理的 BeanDefinition 是基于注解生成的,因此,我们进入第一个if
分支中的parse
方法查看。
// org.springframework.context.annotation.ConfigurationClassParser#parse(org.springframework.core.type.AnnotationMetadata, java.lang.String)protectedfinalvoidparse(AnnotationMetadatametadata, StringbeanName) throwsIOException { processConfigurationClass(newConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER); }
这里将元数据和 Bean 名称封装成了一个ConfigurationClass 对象作为参数,调用了processConfigurationClass
方法。
// org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClassprotectedvoidprocessConfigurationClass(ConfigurationClassconfigClass, Predicate<String>filter) throwsIOException { if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } ConfigurationClassexistingClass=this.configurationClasses.get(configClass); if (existingClass!=null) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } // Otherwise ignore new imported config class; existing non-imported class overrides it.return; } else { // Explicit bean definition found, probably replacing an import.// Let's remove the old one and go with the new one.this.configurationClasses.remove(configClass); this.knownSuperclasses.values().removeIf(configClass::equals); } } // Recursively process the configuration class and its superclass hierarchy.SourceClasssourceClass=asSourceClass(configClass, filter); do { sourceClass=doProcessConfigurationClass(configClass, sourceClass, filter); } while (sourceClass!=null); this.configurationClasses.put(configClass, configClass); }
方法中的第一个if
语句是根据配置类的@Conditional
注解,来判断是否跳过当前配置类的解析,这里的逻辑不在本文的讨论范畴之内,我们假设不跳过,接着分析后续的逻辑。
接下来,会在this.configurationClasses
查找当前的配置类,并赋值给existingClass
变量,当第一次解析配置类的时候,此时的existingClass
一定是空的,因此第二个if
语句块中的步骤不会被执行。
在两个if语句块之后,会递归地通过doProcessConfigurationClass
方法解析配置类及其继承的父类。解析完成之后的配置类对象,会被添加到this.configurationClasses
容器中。
因此,我们接下来重点看一下doProcessConfigurationClass
方法。
这个方法也比较长,其核心逻辑就是对当前配置类的配置信息进行解析和处理,包括递归处理内部类的配置信息、@PropertySource
注解的配置信息、@ComponentScan
注解的配置信息、@Import
注解的配置信息、@ImportResource
注解的配置信息、被@Bean
注解标记的方法,并且,如果包含父类的话,还会返回父类的信息供递归处理。
以上所有被解析的信息,都会作为 ConfigurationClass 对象的内容,保存到解析器的configurationClasses
集合中。
回到最初的processConfigBeanDefinitions
方法中,当parse
方法执行完之后,解析器还会通过validate
方法对解析的结果进行验证,确保解析到的都是符合 Spring 规定的配置。
parser.validate();
随后,还会从解析器中获取所有解析得到的 ConfigurationClass,并排除掉alreadyParsed
集合中的元素,避免后续重复处理。
Set<ConfigurationClass>configClasses=newLinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed);
至此,配置类的解析工作就完成了,解析的结果就是所有已经被处理的配置内容,和一个 ConfigurationClass 结合configClasses
。
@Bean
方法的解析
接着看后面的代码。
// Read the model and create bean definitions based on its contentif (this.reader==null) { this.reader=newConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses);
首先,创建了一个 ConfigurationClassBeanDefinitionReader 类型的reader
对象,然后通过调用它的loadBeanDefinitions
方法,加载configClasses
中的 BeanDefinition。之后,将configClasses
中所有的元素添加到alreadyParsed
中,代表alreadyParsed
中所有的配置类都解析完成了。
虽然在容器进行包路径扫描的时候,所有的配置类都创建了相应的 BeanDefinition,但是配置类中可以通过为一个方法添加@
Bean
注解来配置 Bean 的信息,因此,这一部分的loadBeanDefinitions
方法是为了处理配置类中被标记了@
Bean
注解的方法。
进入loadBeanDefinitions
方法查看源码。
// org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionspublicvoidloadBeanDefinitions(Set<ConfigurationClass>configurationModel) { TrackedConditionEvaluatortrackedConditionEvaluator=newTrackedConditionEvaluator(); for (ConfigurationClassconfigClass : configurationModel) { loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator); } }
这里,对集合中的每一个 ConfigurationClass 调用了loadBeanDefinitionsForConfigurationClass
方法。这里的 TrackedConditionEvaluator 是用来通过@Conditional
注解来判断是不是要跳过当前的配置类处理。
privatevoidloadBeanDefinitionsForConfigurationClass( ConfigurationClassconfigClass, TrackedConditionEvaluatortrackedConditionEvaluator) { if (trackedConditionEvaluator.shouldSkip(configClass)) { StringbeanName=configClass.getBeanName(); if (StringUtils.hasLength(beanName) &&this.registry.containsBeanDefinition(beanName)) { this.registry.removeBeanDefinition(beanName); } this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName()); return; } if (configClass.isImported()) { registerBeanDefinitionForImportedConfigurationClass(configClass); } for (BeanMethodbeanMethod : configClass.getBeanMethods()) { loadBeanDefinitionsForBeanMethod(beanMethod); } loadBeanDefinitionsFromImportedResources(configClass.getImportedResources()); loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()); }
如果当前的配置类是由别的配置类引入的,或者引入了别的配置类,都会有相应的处理。我们此时重点看方法体中for
循环的部分,在这里,会通过配置类的getBeanMethods
方法,将配置类中所有的 BeanMethod 获取到,并对其执行loadBeanDefinitionsForBeanMethod
方法。
从前面对parse方法的解析中可以看到,此处获取到的 BeanMethod 就是当前配置类中所有被标记了@
Bean
注解的方法集合。我们进入loadBeanDefinitionsForBeanMethod
方法中查看源码。
这个方法的代码量也非常可观。但是大部分逻辑都与之前分析过的 BeanDefinition 加载逻辑类似。首先,会读取@
Bean
注解的信息,并根据这些信息创建一个 ConfigurationClassBeanDefinition 类型的对象。然后,将方法作为 BeanDefinition 的工厂方法,因为创建对应的 Bean 实例的时候,就是通过当前的方法来创建的。最后,再通过容器的registerBeanDefinition
方法将 BeanDefinition 注册到容器中。
分析完这部分之后,再次回到processConfigBeanDefinitions
方法查看后续的代码。
处理新增的 BeanDefinition
candidates.clear(); if (registry.getBeanDefinitionCount() >candidateNames.length) { String[] newCandidateNames=registry.getBeanDefinitionNames(); Set<String>oldCandidateNames=newHashSet<>(Arrays.asList(candidateNames)); Set<String>alreadyParsedClasses=newHashSet<>(); for (ConfigurationClassconfigurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } for (StringcandidateName : newCandidateNames) { if (!oldCandidateNames.contains(candidateName)) { BeanDefinitionbd=registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&!alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(newBeanDefinitionHolder(bd, candidateName)); } } } candidateNames=newCandidateNames; }
首先将candidates
集合清空,代表目前candidates
集合中的类都处理完毕了。
接下来,判断容器中的 BeanDefinition 是不是比之前更多了,这里多出来的部分主要就是来自于机遇@
Bean
方法创建的 BeanDefinition。后面的步骤,就是将这些新增的 BeanDefinition 找出来,创建对应的 BeanDefinitionHolder 并添加到candidates
中。
这样,只要candidates
不为空,do-while
循环就会接着以同样的逻辑递归处理这些新增的元素,直到没有新的元素被添加进来。
至此,do-while
循环中的内容就分析完了。
结尾部分
接下来,看方法的最后一部分代码。
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classesif (sbr!=null&&!sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry()); } if (this.metadataReaderFactoryinstanceofCachingMetadataReaderFactory) { // Clear cache in externally provided MetadataReaderFactory; this is a no-op// for a shared cache since it'll be cleared by the ApplicationContext. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); }
最后就是收尾工作,注册了一个 ImportRegistry 单例对象,并清除了缓存信息。
总结
本文分析了processConfigBeanDefinitions
方法中解析配置类的主要逻辑部分,Spring 会递归处理已经筛选过的配置类以及其中包含的@
Bean
注解标记的方法和配置信息。
下一篇开始分析另一个后处理器方法postProcessBeanFactory
。