Spring Boot-18-配置类解析核心方法

接着上文,继续分析配置类的解析的核心方法, 上文中我们留了一个 ConfigurationClassParser.doProcessConfigurationClass() 方法,没有详细分析,下面来看一下这个类具体是怎么处理的

源码跟踪

image

从上往下依次看

内部类处理

先看一下代码片段

image

首先判断有没有 @Component 注解, 有的话进入下面的 processMemberClasses() 方法

image

这里就是对内部类的一个递归处理,我们写个例子看一下

案例

以启动类为例,这里添加两个内部类进去,如下

image

然后在上面的 processMemberClasses() 方法中打个断点

image

这里就获取到两个内部类,然后通过 ConfigurationClassUtils.isConfigurationCandidate() 这个方法做个判断,看是否需要处理,需要的话后面再调用 processConfigurationClass() 方法去处理,这两个方法我们上文中详细分析过了,这里就不再赘述了

上面还有一个 importStack 集合,主要是为了防止相互依赖的问题, 比如 A配置类中依赖B,B配置类中又依赖A

@PropertySource 处理

接着往下看,是 @PropertySource 注解的处理, @PropertySource 可以指定一个配置文件,具体加载就是在这里处理的

image

继续往下

image

首先获取到文件,然后调用了一个 addPropertySource() 方法,点进去

image

这里主要是对环境变量的一些处理

@ComponentScan 处理

再往下看是 @ComponentScan 注解的处理,在启动类中的 @ComponentScan 注解如下

image

这里就配置了个过滤器,用来判断哪些类不需要加载

回到方法中

image

这里的重点是 this.componentScanParser.parse() 这个方法,他会把 @ComponentScan 注解配置的要扫描的包下的所有的 @Component 修饰的类都扫描出来,进入这个方法

image

这里主要是获取一些注解中的属性,然后填充到对象中,比较重要的是这个 basePackages 的获取,这里我们可以在注解中手动指定要扫描的包路径,如果没有手动指定的话,会获取当前类所在的包路径,这里就解释了为什么 Spring Boot 项目的启动类为什么要放在最上层的包中

然后继续往下,是调用了一个 doScan() 方法,点进去

image

这里的重点是 findCandidateComponents() 这个方法, 点进去看一下

image

image

然后这里判断是否要处理的方法是 isCandidateComponent() ,如下

image

这里会调用我们注解中配置的过滤器,判断判断一下是否匹配,然后觉得是否需要处理

案例

这里我们以启动类为例, 看一下他的扫描结果

首先是 scanCandidateComponents() 方法中的返回

image

然后看一下外层的 findCandidateComponents() 的返回

image

@Import 处理

@ComponentScan 处理完成之后会处理 @Import 注解, 会调用一个 processImports() 去处理,主要代码如下

image

这个方法的主要作用是处理 ImportSelector , DeferredImportSelector 还有 ImportBeanDefinitionRegistrar 这几个类的实现, 我们可以通过实现这几个类,往容器中注入 Bean

案例

关于这几个接口的使用,我们写以下几个类来测试一下

首先,新建 MyImportSelector 实现 ImportSelector, 代码如下

1
2
3
4
5
6
7
8
9
10
@Slf4j
public class MyImportSelector implements ImportSelector {

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
log.info(this.getClass().getName());
return new String[]{"com.zhou.springboot.example.model.Test"};
}

}

这个类作用就是将返回的数组中对应的类注册到容器中

然后新建 MyDeferredImportSelector 实现 DeferredImportSelector ,代码如下

1
2
3
4
5
6
7
8
9
10
@Slf4j
public class MyDeferredImportSelector implements DeferredImportSelector {

@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
log.info(this.getClass().getName());
return new String[0];
}

}

这里和上面的 MyImportSelector 的作用其实是一样的

最后一个新建 MyImportBeanDefinitionRegistrar 实现 ImportBeanDefinitionRegistrar ,代码如下

1
2
3
4
5
6
7
8
9
10
@Slf4j
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
log.info(this.getClass().getName());
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
rootBeanDefinition.setBeanClass(Test.class);
registry.registerBeanDefinition("test2", rootBeanDefinition);
}
}

这里也是往容器中注入一个 BeanDefinition ,最后需要使用 @Import 注解把这几个类手动引入才生效, 以启动类为例,修改如下

1
2
3
4
5
6
7
@SpringBootApplication
@Import({MyImportSelector.class, MyDeferredImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class App {
public static void main(String[] args) {
// ... 省略其他代码
}
}

启动项目看一下日志输出

image

这里三个类的作用其实是一样的, 只是处理的顺序不一样 ImportSelector > DeferredImportSelector > ImportBeanDefinitionRegistrar
这种注入方式也不常用,就不详细介绍了

@ImportResource 处理

再往下看,就是对 @ImportResource 这个注解的处理, Spring Boot 中注入 Bean 可以通过注解,也可以通过 xml 的方式, 通过 xml 方式注入的话, 就需要使用 @ImportResource 注解引入具体的 xml ,处理代码如下:

image

这里,最终只会把文件找出来,然后调用 configClass.addImportedResource() 这个方法添加到一个集合中去, 在这一步并不会做具体的处理

这种方式也基本不常用,也不做过多介绍

@Bean 处理

再往下就是对 @Bean 注解的处理,代码如下

image

这里会通过 retrieveBeanMethodMetadata() 这个方法找到这个类中,所有的 @Bean 修饰的方法,然后通过 configClass.addBeanMethod() 添加到一个集合中去, 跟 xml 的方式一样,这里只是保存起来不做具体处理

接口默认方法处理

最后是对接口的默认方法处理,如下

image

点进去如下

image

这些都处理完成之后,会判断有没有父类,有的话返回去,上层方法会继续循环处理这些东西

最后来看一下 xml 和 @Bean 注入的 bean 是什么时候被处理的

回到最外层的 ConfigurationClassPostProcessor$processConfigBeanDefinitions() 这个方法中, 这个方法就是配置类处理的最外层的方法,我们之前最开始分析的, 进到之前说过的那个 do-while 循环中, 如下

image

上面的 parse 方法把这些类都处理完毕后,就会调用 loadBeanDefinitions() 这个方法, 在这个方法中,就会处理 xml 和 所有 @Bean 注入的 bean ,如下

image

总结

本文对上文中的 doProcessConfigurationClass() 方法做了详细的分析, 就是下面这个 8 个模块的处理

image

可以 debug 跟一下效果会更好