文章目录
前言
本篇文章将解析ConfigurationClassPostProcessor这个类解析配置注解时都干了什么。其中会分析以下几点问题:
- @Bean注册Bean到容器中
- @Import导入处理
- @Conditional条件解析
- @ComponentScan注解注册Bean到容器中
- @Configuration配置类的完全模式和轻量模式
- …还有其他配置注解的解析,本篇文章不作分析,只对以上论点进行重点分析。相信读懂本篇文章的读者有能力自主分析其他的配置解析过程
所以,我们将以上几个论点当作问题,带着问题来分析ConfigurationClassPostProcessor这个类。
BeanFactoryPostProcessor处理
首先,ConfigurationClassPostProcessor是一个BeanFactoryPostProcessor,所以它具有在Bean定义阶段动态修改和添加Bean的定义的能力。而处理解析配置注解的开始,正是从postProcessBeanDefinitionRegistry方法开始的。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// ...
processConfigBeanDefinitions(registry);
}
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// 即将被解析的Bean名单
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
// 拿到目前所有的Bean名称
// 需要注意的是,目前你定义的Bean或许只有一个,就是引导类那个Configuration(如果是springboot的话)
// 因为在引导类你将其显式的注册进了Spring上下文
// 现在还没进行@Bean、@ComponentScan、自动装配之类的注册Bean
// 所以经过接下来的操作,将动态注册一堆Bean上去
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
// 变为Bean定义对象
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
// 这里是重复判断,因为接下来判断如果是配置类需要被解析,会在Bean定义中增加属性
// 这里就根据Bean定义查找是否有对应属性,如果有表示已经处理过了
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) ||
ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
// 判断是否是配置类
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
// 如果是,添加进处理名单中
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// Return immediately if no @Configuration classes were found
// 名单是空的,也就没有必要往下解析了
if (configCandidates.isEmpty()) {
return;
}
// Sort by previously determined @Order value, if applicable
// 这里看出,解析的时候是有序的
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// ...
// Parse each @Configuration class
// 最主要的解析类,由它负责解析以上获得的Bean名单
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
// 这里是一个循环,具体为什么是循环在下面的解析中会解释到
// 这里先埋下一个伏笔,记住即可
do {
// 解析工作
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// ...
// 将解析到的类注册进IOC容器中
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
// ...
}
while (!candidates.isEmpty());
// ...
}
我这里省略了一些代码,只看主干部分。
判断是否需要被解析
首先看一下判断是否是配置类的逻辑
ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)
如果判断是配置类,才会继续往下解析,所以这里看一下其是如何判断的
if (isFullConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
else if (isLiteConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
Full完全模式与Lite轻量模式
这里省略了一些获取注解信息的过程,主要是isFullConfigurationCandidate和isLiteConfigurationCandidate这两个方法进行判断的
public static boolean isFullConfigurationCandidate(AnnotationMetadata metadata) {
return metadata.isAnnotated(Configuration.class.getName());
}
private static final Set<String> candidateIndicators = new HashSet<>(8);
static {
candidateIndicators.add(Component.class.getName());
candidateIndicators.add(ComponentScan.class.getName());
candidateIndicators.add(Import.class.getName());
candidateIndicators.add(ImportResource.class.getName());
}
public static boolean isLiteConfigurationCandidate(AnnotationMetadata metadata) {
// Do not consider an interface or an annotation...
if (metadata.isInterface()) {
return false;
}
// Any of the typical annotations found?
// 此集合为上面所述的那几个注解
for (String indicator : candidateIndicators) {
if (metadata.isAnnotated(indicator)) {
return true;
}
}
// Finally, let's look for @Bean methods...
try {
return metadata.hasAnnotatedMethods(Bean.class.getName());
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
}
return false;
}
}
从这里可以看出来,有两种配置解析模式:
-
如果类被打上@Configuration,此为完全解析模式(Full)
-
如果此类带有以上4个注解或者有存在方法被打上@Bean注解的,都算配置类,都需要被解析,此为轻量解析模式(Lite)
那么有什么区别呢?完全解析模式会使用动态代理的方式将配置类进行动态代理,可以有如下操作
@Bean
public Test test(){
Test test = new Test();
test.setUser(user());
return test;
}
@Bean
public User user(){
return new User("xx");
}
Test Bean的依赖User对象,直接调用方法名即可,会将其注入Spring中的Bean,如果是lite模式,就不会注入Spring中的Bean,而是新new一个对象,我们可以试验一下
@EnableAutoConfiguration
public class TestAutoConfigure {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(TestAutoConfigure.class)
// 非WEB
.web(WebApplicationType.NONE)
.run(args);
Test test = context.getBean("test", Test.class);
System.out.println(test.getUser());
System.out.println(context.getBean("user", User.class));
context.close();
}
@Bean
public Test test(){
Test test = new Test();
test.setUser(user());
return test;
}
@Bean
public User user(){
return new User("xx");
}
}
此模式为lite模式,因为配置类并没有@Configuration注解,此时控制台打印
可以看到,两个User对象是不同实例的,其中有一个对象不是Spring中的User。那么我们此时加上@Configuration注解在引导类上,会是怎样的结果呢?
可以看到,此时两个User都是一个实例,都为Spring中的Bean了。由此可以看出完全模式和轻量模式的区别。轻量模式由于没有被动态代理,看起来比较轻量一点,所以叫做轻量模式。但它不享受这种方法调用的装配方式。而完全模式则会被CGLIB动态代理,我们来试验一下
System.out.println(context.getBean(TestAutoConfigure.class));
首先从上下文中拿到我们的引导类这个Bean,打印其className
可以看到,此时是被动态代理过的。
解析BeanDefinition
让我们回到解析配置注解的主干上。在解析过程中,值得一提的是,解析过程是一个循环,在此留下这个疑问即可,继续往下看。我们知道,最终会走到parser的parse这个解析方法中去
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
// 下面三种方式解析Bean,其实都差不多,不同方式都是一个结果
// 这里不求甚解
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
// 这里需要被读者记住,这里将延时import在解析完所有之后会进行处理
this.deferredImportSelectorHandler.process();
}
如果读者有看过上一篇自动挡装配Enable模式的文章,就会知道其中Import还可以延时处理,其中延时的Imoport在解析过程中是不进行处理的,而是放入一个队列中,在所有解析完成之后才进行处理,而处理的时机就在这里。这里我们继续往下看
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
// @Conditional注解的条件过滤
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
// ...
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
do {
// 主要解析方法
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
// 若解析完成,将其放入configurationClasses这个集合中去
this.configurationClasses.put(configClass, configClass);
}
这里值得一提的就是@Conditional注解的条件过滤,下面我们来详细分析一下
@Conditional条件过滤
首先直接进入shouldSkip方法
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
// 如果没有注解元信息,或者没有被标注@Conditional注解,直接跳过,不进行过滤
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
// ...
List<Condition> conditions = new ArrayList<>();
// 获取condition类的全限定类名
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
// 将其实例化出来,加入集合中去
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
// 排序
AnnotationAwareOrderComparator.sort(conditions);
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
// 执行匹配方法condition.matches
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
这里值得一提的是,getConditionClasses方法,此方法可以拿到Conditional类的全类名,什么意思呢?这里举一个例子,像我们熟知的@ConditionalOnBean、@ConditionalOnClass,都是@Conditional的派生注解,其作用都是例如假设当某Bean存在时、当某Class存在时:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
// ...
}
可以看到,其元标注了@Conditional,其实根本还是在@Conditional注解上,而上面所说的getConditionClasses方法,正是拿到了@Conditional中的value(OnBeanCondition.class),总之,由以上逻辑来看,若是ConditionalOnBean注解,就会拿到OnBeanCondition这个类并且实例化出来,执行其matches方法,所以问题的核心就在于,@Conditional注解中的value的match方法,其返回值决定了该如何过滤Bean的加载。有兴趣的读者可以进一步阅读OnBeanCondition的match方法,其主要是判断BeanFactory中是否有对应的BeanDefinition。
可以看到,在方法参数中最后一个参数是表示阶段的,其中阶段有两种:
- PARSE_CONFIGURATION :解析配置阶段
- REGISTER_BEAN :注册Bean阶段
以上都只是解析阶段,所以参数传的是PARSE_CONFIGURATION。
有些条件过滤是看阶段的,比如@ConditionalOnBean注解,判断Bean阶段不应该是解析配置阶段,而应该是注册Bean阶段,因为解析阶段可能Bean都还没完全解析完,不应该过早进行判断,到了注册Bean阶段,就可以是解析完所有Bean的合适时机了,此时机就可以进行条件过滤。
下面我们回到解析主干中去。
解析配置注解
接下来,进入doProcessConfigurationClass方法,看看是如何解析配置注解的
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// 如果是Component注解,递归解析之
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);
}
// Process any @PropertySource annotations
// 处理@PropertySource注解
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");
}
}
// Process any @ComponentScan annotations
// 处理@ComponentScan注解
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) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
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
// 处理@Import注解
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Process any @ImportResource annotations
// 处理@ImportResource注解
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
// 处理@Bean注解的方法
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();
}
}
// No superclass -> processing is complete
return null;
}
在这里,我们可以很清晰的看出处理的脉络,接下来根据我们所感兴趣的模块进行一一分析
@ComponentScan注解的解析
这个注解主要是扫描指定包名,注册Bean到IOC容器中。我们熟知的@Component以及其派生类例如@Service、@Controller、@Configuration等等都是此注解扫描的,并将其注册到Spring中。
// Process any @ComponentScan annotations
// 这里主要拿到@ComponentScan注解的元信息,里面包含注解中的属性值
// 如果是ComponentScans,也会分解成一个个的ComponentScan
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
// 从这里可以看出,这里也会进行@Conditional的条件过滤
// 并且阶段是REGISTER_BEAN,表示要开始注册Bean了
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
// 扫描并注册Bean
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());
}
}
}
}
可以看到,在扫描并注册后,还会判断是否是需要被配置的Bean,如果是就继续进行此Bean的处理。
我们这里关键看this.componentScanParser的parse方法,扫描并注册Bean
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
// 主要扫描的类
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
// ...略过一些ComponentScan注解属性的配置
Set<String> basePackages = new LinkedHashSet<>();
// 获取basePackages属性值
// 下面主要针对属性值,获取需要被扫描的包名
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);
}
// 获取需要被注册的Class
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 如果没有配置需要扫描的包,就使用declaringClass的包名
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
// ...
// 扫描并注册
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
值得一提的是,如果没有配置包名,将扫描默认包,而这个默认包值declaringClass是多少呢?可以回顾上面,此参数是配置类的全限定类名
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
sourceClass即为当前我们正在解析的配置类,而此类名将解析为包名,作为默认的扫描包名
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
public static String getPackageName(String fqClassName) {
Assert.notNull(fqClassName, "Class name must not be null");
// 拿到最后一个.的位置
int lastDotIndex = fqClassName.lastIndexOf(PACKAGE_SEPARATOR);
// 截取,这样包名就有了
return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : "");
}
同时,这也是SpringBoot可以扫描Bean的原理,可以知道,在springboot引导类的注解上有@SpringBootApplication注解,而此注解元标注了@Component注解
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
}
看到这里,就可以理解为什么SpringBoot中,我们的引导类一定要放在包外面,这样,引导类之内的包都可以扫描到@Service、@Controller之类的Bean。就像下图所示的那样
因为它会默认扫描@ComponentScan所在类的包。
至于扫描并注册的方法scanner.doScan这里不多赘述,此内容是AnnotationConfigApplicationContext注解配置上下文的内容。
@Bean方法的解析
这里回到解析主干上,可以知道,解析@Bean注解的过程如下
// Process individual @Bean methods
// 将配置类中@Bean注解的方法拿出,并封装为MethodMetadata对象
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
// 添加到ConfigurationClass对象中
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
这里主要是拿到@Bean标注的方法,然后放入ConfigurationClass对象中,在后面注册Bean时会拿出来,并执行方法注册Bean,在后面的loadBeanDefinition方法会看到。
@Import注解的解析
此注解的解析在上一篇文章中,分析Enable模式的时候已经介绍过了,在这里会进行更详细的一个分析。接下来直接进入解析方法
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
// importCandidates集合就是Import的value
if (importCandidates.isEmpty()) {
return;
}
// ...
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
// 判断是否是ImportSelector的实现
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
// 实例化成ImportSelector类
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
// 如果有aware方法,在这里会进行aware设置
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
// 延迟导入,其中ImportSelector接口有一个子类是DeferredImportSelector
// 表示该Import要延迟导入,此时不做解析,只是将其保存在一个集合中去
// 在所有配置类解析完成之后,才会对DeferredImportSelector实现类要导入的类进行解析
if (selector instanceof DeferredImportSelector) {
// 封装DeferredImportSelector类,将其信息保存到集合中
this.deferredImportSelectorHandler.handle(
configClass, (DeferredImportSelector) selector);
}
// 若不是延迟导入,那就直接开始导入工作
else {
// 调用selector的方法,获取需要被注册的Bean的类名
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 递归解析该需要被注册的Bean
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
// 又或者是ImportBeanDefinitionRegistrar的实现?
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
// 这里的做法是先将ImportBeanDefinitionRegistrar存放起来
// 在稍后一点再调用其方法注册BeanDefinition
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
// 到这里,判断不是以上的接口编程实现
// 或者是上面import接口编程返回出来的需要被导入的那些类,也会走到这一步
// 将此类当作配置类解析
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
// 接下来还会到doProcessConfigurationClass方法再进行解析
// 还会走一样的流程进行解析配置注解
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
// ...
}
此方法可以总结为如下三个判断:
- 如果是ImportSelector的子类
- 如果是DeferredImportSelector,延迟Import,加入集合。至于延迟加载的处理时机,在解析的开头就有埋下伏笔,记得的读者可以回过头去看看
- 如果不是延迟import,直接执行接口方法,获取需要被导入的Bean类名,递归Import之,若返回出来的类名是普通类,或者是配置类,递归之后会进入第三个分支
- 如果是ImportBeanDefinitionRegistrar的子类
- 将其保存下来,在后面载入BeanDefinition方法会进行调用其接口的方法注册Bean,在后面我们就可以看到其调用时机了
- 如果不是上述两种情况,将把当前类当作一个Configuration配置类一样进行递归解析
在解析过程中,有较多的递归,难免会绕晕,建议读者在大脑中反复模拟方法的运转,利用假设法来推导一个例子到某一步将会发生怎样的操作,大致跑一遍大概也就会理解这其中的奥秘了。
延迟导入
回顾我们刚开始导入的地方,有这么一段代码
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
// ...
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
// 延迟导入的处理
this.deferredImportSelectorHandler.process();
}
最终会走到processGroupImports方法中去
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(
entry.getMetadata());
try {
// 还是执行上面的导入方法
processImports(configurationClass, asSourceClass(configurationClass),
asSourceClasses(entry.getImportClassName()), false);
}
// ...
});
}
}
最终还是会执行我们上面分析过的processImports方法,所以延迟导入其实和普通导入无异,只不过时机不同。
到这里,我们要分析的解析过程就差不多结束了,当然还有其他注解的解析,这部分就留给读者自主完成,这里进行解析的总结
解析总结
- @ComponentScan:直接将扫描的Bean注册到IOC容器中,默认扫描路径是打上该注解的类所在的包
- @Bean:存放在ConfigurationClass类属性中,稍后处理
- @Import:递归导入所需要被导入的类,其中有延时导入,在后面会分析到
回到解析方法中,可以看到,经过我们这么一导入,就会将导入构造的ConfigurationClass类放入ConfigurationClassParser解析类的成员变量configurationClasses中
this.configurationClasses.put(configClass, configClass);
所以,现在configurationClasses集合中存放着一堆被解析出来的Bean,其中延迟导入的那些Bean在集合的最底层部分,注册时机会比普通Bean都稍微晚一点,因为configurationClasses的数据结构是有序的
private final Map<ConfigurationClass, ConfigurationClass> configurationClasses = new LinkedHashMap<>();
知道这个是非常重要的,因为我们熟知的SpringBoot自动装配中,正是一个延迟导入的实现,至于为什么,将在解析@ConditionalOnBean注解失效问题具体说明。
注册Bean阶段
以上就完成了解析工作,接下来进入到将解析到的配置信息,都注册到IOC容器中去。这里我们回到最开始的processConfigBeanDefinitions方法中,其中do-while循环中有这么一段方法
this.reader.loadBeanDefinitions(configClasses);
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
// 和Conditionnal同理
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
// 遍历加载ConfigurationClass集合
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// Conditional条件过滤
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
// 如果该类是被Import进来的
if (configClass.isImported()) {
// 将其注册到IOC容器中
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
// 注册那些Bean方法到IOC容器中
loadBeanDefinitionsForBeanMethod(beanMethod);
}
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
// 此时会处理我们上面说的还未处理的ImportBeanDefinitionRegistrar
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
这里可以看看loadBeanDefinitionsFromRegistrars方法的处理
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry));
}
可以看到,ImportBeanDefinitionRegistrar的registerBeanDefinitions方法将在此时机进行调用。
以上注册Bean的逻辑较为复杂,大多是有关Spring的操作,由于这里主要解析注解驱动,所以对Spring是如何注册Bean的不作过多赘述,读者此时只需要知道在这里将注册BeanDefinition到BeanFactory即可。
注解驱动处理总结
到这里就差不多完成了注解的解析和注册工作,接下来我们回到ConfigurationClassPostProcessor的processConfigBeanDefinitions方法,其中do-while循环有这么一段逻辑:
do {
parser.parse(candidates);
parser.validate();
// 拿到我们上面说的ConfigurationClasses集合
// 此集合存放着解析到的配置类
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
// 注册解析到的Bean
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
// candidateNames.length是未解析之前Bean的数量
// registry.getBeanDefinitionCount()是现在Bean的数量
// 主要是以Bean数量判断是否有新的Bean注册到IOC容器中去了
// 也就是说,这里开始操作那些,新注册进来的Bean
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
for (String candidateName : newCandidateNames) {
// 如果旧的不存在,证明此时的Bean即为新注册的Bean
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
// 加入需要被解析的集合中去
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
}
while (!candidates.isEmpty());
可以看到,这里是一个循环,为什么要这么做呢?首先我们来捋一捋整个的流程:
- 解析需要被解析的Bean
- 注册那些解析出来的Bean
- 将上面流程注册到的Bean再加入candidates队列中,回到第一步,解析这些Bean
- 注册到的Bean的来源可以是@Import导入进来的,@Bean方法定义的,@ComponentScan扫描注册进来的等等
- 上述三个步骤一直循环,直到candidates队列为空,也就是说解析不到新的Bean为止
在整个解析过程中有很多地方做了去重,和判断是否已经解析过的逻辑,所以有的地方看似会重复注册,但其实不会。完整的看下来,可以发现有很多地方都用了递归的操作,第一次看这个类的源码难免会有点难以理解,希望读者可以用真实Bean的例子在脑海中反复跑一遍流程,反复揣摩,阅读源码难免会有一点难度,最重要的就是心静,耐心的好好推敲,反复阅读。