为了给组里的实习生妹妹讲如何自定义Springboot的Starter,
我觉得需要让她先知道Springboot的自动装配机制,
但又感觉是不是得先让她了解Spring的SPI机制,
最后又觉得还有必要说明一下一个@Configuration配置类在Spring中如何被解析,这又会引出对ConfigurationClassPostProcessor的讲解,
完了,收不住了,到处都是知识盲区。
知识盲区脑图如下。
前言
本篇文章将详细分析Spring中如何加载并解析由@Configuration注解修饰的配置类。
@Configuration注解是Spring中特别重要且知名的注解,大家都知道怎么使用,作用就是可以向容器注册bean,并且就像“每一个成功的男人背后都有一个优秀的女人在支持”,@Configuration注解的背后也有一个功能强大的类在支撑,那就是ConfigurationClassPostProcessor,所以本文的重点就是ConfigurationClassPostProcessor如何解析由@Configuration注解修饰的配置类。
本文的一个思维导图如下所示。
最后,请务必知道Spring的BeanDefinition是什么,不能只知道Spring有bean但不知道bean的前生BeanDefinition
Springboot版本:2.4.1
Spring版本:5.3.2
正文
一. 示例工程搭建
在进行ConfigurationClassPostProcessor源码分析前,需要搭建示例工程来演示ConfigurationClassPostProcessor的执行逻辑。
定义如下几个业务类。
public class MyController {}
@Service
public class MyService {}
public class MyDao {}
复制代码
配置类如下所示。
@ComponentScan
@Configuration
@Import({MyImportBeanDefinitionRegistrar.class,
MyImportSelector.class})
public class MyConfig {
@Bean
public MyDao myDao() {
return new MyDao();
}
}
复制代码
MyImportBeanDefinitionRegistrar实现了ImportBeanDefinitionRegistrar接口,定义如下。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = new RootBeanDefinition(MyController.class);
registry.registerBeanDefinition("myController", beanDefinition);
}
}
复制代码
MyImportSelector实现了ImportSelector接口,定义如下。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {
"com.learn.beanfactory.postprocessor.further.MyFurtherConfig"
};
}
}
复制代码
上面中的MyFurtherConfig也是一个配置类,定义如下。
@Configuration
public class MyFurtherConfig {
@Bean
public MyFurtherService myFurtherService() {
return new MyFurtherService();
}
}
复制代码
MyFurtherService是一个业务类,定义如下。
public class MyFurtherService {}
复制代码
测试类定义如下。
public class MyTest {
public static void main(String[] args) {
ApplicationContext applicationContext
= new AnnotationConfigApplicationContext(MyConfig.class);
}
}
复制代码
示例工程目录结构如下所示。
二. BeanFactoryPostProcessor
Spring中提供了大量的扩展点,而ConfigurationClassPostProcessor的扩展点类型叫做bean工厂后置处理器,也就是BeanFactoryPostProcessor。
先观察一下BeanFactoryPostProcessor的类图,如下所示。
BeanFactoryPostProcessor接口允许其实现类修改注册表中的BeanDefinition,这里的注册表指ConfigurableListableBeanFactory接口的实现类,通常为DefaultListableBeanFactory(如果不知道这是什么,可以就理解为我们常说的Spring容器)。
Spring启动并初始化容器阶段,会调用到AbstractApplicationContext#refresh方法来刷新容器,在AbstractApplicationContext#refresh方法中,会先执行invokeBeanFactoryPostProcessors() 方法来执行每一个BeanFactoryPostProcessor的逻辑,并且这一步在初始化bean之前完成。
refresh() 方法简要表示如下。
public void refresh() throws BeansException, IllegalStateException {
......
// 执行bean工厂后置处理器逻辑
invokeBeanFactoryPostProcessors(beanFactory);
......
// 添加bean后置处理器到容器
registerBeanPostProcessors(beanFactory);
......
// 初始化bean
finishBeanFactoryInitialization(beanFactory);
......
}
复制代码
既然BeanFactoryPostProcessor接口允许其实现类修改注册表中的BeanDefinition,但是在Spring启动并且初始化容器的最开始阶段,Spring容器持有的注册表DefaultListableBeanFactory中是没有BeanDefinition的,那么肯定有一个时机存在一个类完成了将BeanDefinition加载到注册表中,时机就是invokeBeanFactoryPostProcessors() 方法的调用,类则是ConfigurationClassPostProcessor。
ConfigurationClassPostProcessor用于解析由@Configuration注解修饰的类(称为配置类),其类图如下所示。
ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor接口,BeanDefinitionRegistryPostProcessor接口继承于BeanFactoryPostProcessor接口,所以ConfigurationClassPostProcessor有如下两层身份。
- ConfigurationClassPostProcessor是一个bean工厂后置处理器;
- ConfigurationClassPostProcessor具备向容器注册BeanDefinition的功能。
所以在Spring启动并初始化容器的最开始阶段,Spring会执行ConfigurationClassPostProcessor的逻辑来解析配置类,并且Spring还会通过流程控制,让BeanDefinitionRegistryPostProcessor的执行先于非BeanDefinitionRegistryPostProcessor的bean工厂后置处理器。
小结
- BeanFactoryPostProcessor叫做bean工厂后置处理器,ConfigurationClassPostProcessor是由Spring提供的可以实现向容器注册BeanDefinition功能的bean工厂后置处理器;
- ConfigurationClassPostProcessor的执行先于非BeanDefinitionRegistryPostProcessor的bean工厂后置处理器;
- ConfigurationClassPostProcessor向容器注册BeanDefinition是通过解析配置类来实现。
三. BeanFactoryPostProcessor执行顺序
前文已知ConfigurationClassPostProcessor就是一个Spring内置的BeanFactoryPostProcessor,但是Spring中有那么多的BeanFactoryPostProcessor,包括Spring内置提供的,用户自定义的,这些BeanFactoryPostProcessor之间的执行肯定有一个先后,本节将对BeanFactoryPostProcessor执行顺序进行一个分析。
在示例工程中,创建AnnotationConfigApplicationContext容器时传入了MyConfig配置类的Class对象,称创建容器时传入的配置类为初始配置类,下面再给出初始配置类MyConfig的定义,如下所示。
@ComponentScan
@Configuration
@Import({MyImportBeanDefinitionRegistrar.class,
MyImportSelector.class})
public class MyConfig {
@Bean
public MyDao myDao() {
return new MyDao();
}
}
复制代码
在MyConfig的定义中,使用了如下注解。
- @ComponentScan注解。默认将配置类所在包及其子包下所有由@Component,@Controller,@Service,@Repository,@Configuration注解修饰的类注册为容器中的bean;
- @Configuration注解。表示当前类是一个配置类,配置类也会被注册为容器中的bean;
- @Import注解。通过@Import注解可以导入ImportBeanDefinitionRegistrar和ImportSelector的实现类,并通过它们的实现类来向容器注册bean或配置类;
- @Bean注解。在配置类中所有由@Bean注解修饰的方法的返回值,会被注册为容器中的bean。
在配置类中,有两种方式来向容器注册bean。
- 通过配置类直接向容器注册bean。例如使用@Bean注解,使用@ComponentScan注解和使用@Import(业务类.class);
- 通过配置类向容器注册配置类从而间接注册bean。被注册的配置类也会被解析,从而被注册的配置类所配置的bean也会被注册到容器中,Springboot中的自动装配就是这样实现的。
上述讨论的通过配置类向容器注册bean,并且可以使用多种注解和基于多种方式,能够实现这样的功能,实际是依赖的ConfigurationClassPostProcessor。在Spring中,new一个容器就会触发这个容器的初始化逻辑,例如示例工程中AnnotationConfigApplicationContext的构造函数会调用到AbstractApplicationContext的refresh() 方法,在refresh() 方法中会在初始化bean