
本文是spring框架源码学习系列的第一篇。
当然,也并非那种吃透代码、扣细节程度的分析,只是单纯从一个开发者的角度,简单研究一些常用的组件的创建,添加和运作方式。
从整体上认识框架,了解其工作原理,对排查和解决问题都很有帮助,提升框架使用熟练度的同时,也可以在一定程度上定制和拓展框架,所以个人认为,这是有必要的一环。
文章要分析的是比较接近框架“底层”的源码。一般来说,从java类转为spring bean要经历一个过程:
java类 -> 字节码 -> class -> bean定义 -> ioc -> bean -> ioc
也就是说,在spring中,是先有bean定义,才有bean对象,意识到这一点很重要。
但本文研究的只是其中的一部分,从class到ioc,即: 字节码 -> class -> bean定义,主要是关于spring如何创建bean定义,以及如何加入ioc容器的问题,因此相对来说没那么复杂。
以下将尽可能排除其他干扰因素,用一个最简单的spring应用为案例,来分析这个转换的过程。
bean定义,即BeanDefinition,是spring框架中的内部数据结构,它是对一个bean的元数据描述(Metadata),如类信息、作用域、构造参数、依赖、属性、初始化方法等等,spring会根据bean定义来实例化bean
一、项目结构
.
|___debug-5.iml
|___pom.xml
|___src
| |___test
| | |___java
| |___main
| | |___resources
| | | |___application.properties
| | |___java
| | | |___com
| | | | |___err0l
| | | | | |___spring
| | | | | | |___debug5
| | | | | | | |___HelloWorld.java
| | | | | | | |___Debug5Application.java
| | | | | | | |___TestController.java
| | | | | | | |___AppConfig.java
其中,AppConfig为主配置类,Debug5Application为启动类,HelloWorld和TestController是没有任何业务逻辑的普通类。
// 配置类; 启用组件扫描功能;
@Configuration
@ComponentScan
public class AppConfig {
}
// 启动类
public class Debug5Application {
public static void main(String[] args) {
// 使用基于注解的ApplicationContext
final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
final String[] definitionNames = context.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
}
}
@Component
public class HelloWorld {
public String key = "hello, ";
public String value = "world";
}
@Controller
public class TestController {
}
pom.xml里也只有spring的核心依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.2.8</version>
</dependency>
</dependencies>
二、注册ConfigurationClassPostProcessor
ConfigurationClassPostProcessor是一个BeanFactoryPostProcessor接口的实现类,是spring的核心组件之一,用于处理使用@Configuration注解标注的配置类,如AppConfig。
因此,要分析bean定义的注册过程,需重点关注该类。
同时,它也是一个spring bean,或者说,任何受spring管理的对象都称为bean,所以也需要注册进ioc,其调用栈为:
- new AnnotationConfigApplicationContext(AppConfig.class);
-- this();
--- new AnnotatedBeanDefinitionReader(this);
---- AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
图2-1 给postProcessBeanDefinitionRegistry()设置断点
图2-2 注册ConfigurationClassPostProcessor的bean定义信息
安装好依赖,并在ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry()方法里打上断点后,并启动应用。
BeanFactoryPostProcessor是spring框架中的一个扩展点,主要作用是在ioc容器实例化任何bean之前,读取并修改容器中已注册的bean定义的元数据信息(包括注册新的定义)
三、调用BeanFactoryPostProcessor
BeanFactoryPostProcessor的调用先于任何bean相关的调用逻辑之前。
- new AnnotationConfigApplicationContext(AppConfig.class);
-- refresh();
--- invokeBeanFactoryPostProcessors(beanFactory);
---- PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
----- invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
------ postProcessor.postProcessBeanDefinitionRegistry(registry);
图3-1 执行到postProcessBeanDefinitionRegistry()方法中断点处
四、调用ConfigurationClassPostProcessor
1、筛选配置类
spring会先将ioc容器中的所有bean定义都当做是配置类候选,但经筛选后,只剩AppConfig。
图4-1 筛选候选
顺便一提,AppConfig的bean定义是在实例化AnnotationConfigApplicationContext时加入ioc容器的
2、解析配置类
接着解析配置类候选。
图4-2 解析候选
将配置类bean定义包装为ConfigurationClass后再处理。
- ConfigurationClass configClass = new ConfigurationClass(beanDef.getMetadata(), beanName, (beanDef instanceof ScannedGenericBeanDefinition));
- processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
-- doProcessConfigurationClass(configClass, sourceClass, filter);
3、扫描组件
查找当前bean定义中是否使用了@ComponentScan注解,说白了就是检查是否启用组件扫描功能。
图4-3 查找@ComponentScan
结合前文可知,AppConfig类确实带有@ComponentScan注解,因此,componentScans不会为空,进而开始以当前SourceClass为基础,扫描其他组件。
图4-4 扫描组件
如果未设置@ComponentScan的basePackages属性的话,则取当前配置类的所在包为基础路径。
图4-5 获得包名
图4-6 根据包名查找
其规则为,类路径、包名、资源名拼接在一起的表达式。
4、加载字节码文件
classpath*:com/err0l/spring/debug5/**/*.class
图4-7 获得资源文件
5、创建bean定义
进一步判断,如果符合条件的话,如@Component、@Configuration、@Service、@Controller等,就将其包装为bean定义,并加入candidates待返回上层。
图4-8 判断是否为组件(候选)
如,本文代码中的HelloWorld、TestController。
图4-9 将字节码对应的class对象包装为bean定义信息
6、将bean定义加入ioc
findCandidateComponents()方法返回调用后,进一步处理候选对象,如判断ioc容器中是否已经存在了同名的bean定义等。
图4-10 判断候选是否符合加入ioc的条件
如果checkCandidate(beanName, candidate)为true,则最终将bean定义信息加入ioc容器。
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
// 将definitionHolder加入registry(ioc)
registerBeanDefinition(definitionHolder, this.registry);
}
7、进一步处理bean定义
类似的,在this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName())返回后,对已加入ioc容器的bean定义(scannedBeanDefinitions)进行处理。
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
// ...
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());v
}
}
因为加入ioc容器的bean定义本身,也可能是配置类,所以需要进一步处理。
图4-11 判断已解析的bean定义是否为配置类-1
如果是配置类的话,就以解析配置类的方式,对其进行解析:
- ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)
- parse(bdCand.getBeanClassName(), holder.getBeanName());
-- ConfigurationClass configClass = new ConfigurationClass(reader, beanName);
-- processConfigurationClass(configClass, DEFAULT_EXCLUSION_FILTER);
图4-12 判断已解析的bean定义是否为配置类-2
至于判断条件,首先看bean定义是否使用了@Configuration注解,其次看是否使用了@ImportResource、@Import、@Component(包括其他派生注解)、@ComponentScan,最后看里是否含有@Bean标注的方法。
也就是说,使用了@Component或其派生注解的类,都会被spring看做是一个配置类,更准确地说,是一个轻量级的配置类。
// CONFIGURATION_CLASS_ATTRIBUTE: configurationClass
// CONFIGURATION_CLASS_LITE: lite
if (config != null || Boolean.TRUE.equals(beanDef.getAttribute(CANDIDATE_ATTRIBUTE)) || isConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
}
8、@Import相关
@ComponentScan注解相关的逻辑执行完后,开始处理@Import注解。
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
@Import是spring提供的另一种导入组件的方式,主要是为了导入第三方组件而设置,使用时需要实现ImportSelector或ImportBeanDefinitionRegistrar接口,像springboot就是用这种方式来整合第三方依赖。
由于本文并没有用到,所以... 略。
基本的原理都是一样的,筛选 -> 解析 -> 加入ioc
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class<?>[] value();
}
9、@ImportResource相关
@ImportResource也一样,略。
AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
// ...
10、@Bean相关
1) 包装@Bean方法为BeanMethod
接着处理@Bean注解相关部分,如果存在,则包装为BeanMethod类型,并加入当前配置类中(但未调用)。
图4-13 获取@Bean标注的方法
在以上步骤都运行后,调用当前节点的最终处理方法(不过,因为本案例比较简单,这个方法里什么都没做)。
图4-14 调用deferredImportSelectorHandler.process()方法
2) 将BeanMethod包装为bean定义,并加入ioc
主配置类解析完毕,最后的最后,加载配置类中附加的bean定义,如@Bean标注的方法等。
图4-15 调用this.reader.loadBeanDefinitions(configClasses)
如果存在BeanMethod的话,就将其包装为bean定义,并加入ioc容器。
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
至此,关于spring装载bean定义的源码分析到尾声了。
四、结语
本文粗浅地分析了从创建bean定义到加入ioc容器的过程。
其中的关键在于ConfigurationClassPostProcessor类,它是spring解析组件的入口,以及@ComponentScan注解,只有启用了该注解,spring才会扫描除主配置类以外的组件。
有多种定义(标记)组件的方式,如@Component、@Configuration、@Bean等,使用这些注解标注的类编译为字节码后,在运行时通过类加载器获取得,并且会被包装为bean定义加入ioc容器。
同时存在先后顺序:使用@Component及其派生注解的对象是首选,其次才是@Bean。
后续会通过这些bean定义信息来实例化组件(本文未涉及的部分)。
以上。