Spring在配置IOC的时候有两种配置方式,一种是javaConfig的方式,一种是xml配置方式,那在配置完之后Spring是如何去解析的呢
本篇文章只讲解Spring如何解析javaConfig配置的
其实Spring在解析配置类的时候会经过这么几个步骤:初始化 -> 读取配置文件 -> 解析配置文件->扫描类->注册BeanDefinition -> 创建Bean
虽然过程看起来是非常的简单,但是里面会涉及到很多的业务逻辑
ApplicationContext的初始化
在讲ApplicationContext的初始化之前先来了解下我们使用Spring创建Bean的时候所需要用到的ApplicationContenxt
public class IOCDemo {
public static void main(String[] args) {
//JavaConfig的配置方式
AnnotationConfigApplicationContext context1 = new AnnotationConfigApplicationContext(IOCConfig.class);
//XML的配置方式
ClassPathXmlApplicationContext context2 = new ClassPathXmlApplicationContext("classpath:IOCConfig.xml");
}
}
我们不管是使用XML的方式来配置还是使用javaConfig的方式来配置,在获取Bean的时候都会使用这个ApplicationContenxt的实现类
那么这里,我们主要看看AnnotationConfigApplicationContext这个类的继承关系图(因为本文主要是围绕AnnotationConfigApplicationContext讲的)
这里AnnotationConfigApplicationContext除了实现了ApplicationContext接口之外还是实现了BeanDefinitionRegisstry这个接口,这也就是为什么AnnotationConfigApplicationContext会自带BeanDefinition注册功能
那除了这些接口之外我们还需要看一个重要的接口那就是BeanFactory,这个接口是Spring的顶层接口,主要就是用来创建Bean的,因此他在整个IOC的过程中处于一个非常重要的地位
ClassPathXmlApplicationContext这个实现类的继承关系也是同样实现了BeanFactory和ApplicationContext接口,这里大家稍作了解即可
那在了解完ApplicationContext类之后,我们再来看看AnnotationConfigApplicationContext是如何初始化的以及初始化的时候主要做了些什么事情
那么我们通过有参来构造AnnotationConfigApplicationContext对象的时候,它会直接调用他的有参构造函数,那么在有参构造函数里会初始化三个比较重要的类-----DefaultListableBeanFactory、AnnotatedBeanDefinitionReader、ClassPathBeanDefinitionScanner
首先是通过this()去调用,如果有父类的话会先去调用父类的无参构造函数然后再调用本类的无参构造函数,这个类它是继承了GenericApplicationContext这个类,因此会优先调用这个类的无参构造函数初始化了一个DefaultListableBeanFactory类,这个类是BeanFactory的一个实现
之后会在本类的无参构造函数里初始化AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner,我们称这两个类为读取器和扫描器
AnnotatedBeanDefinitionReader这个类主要就是用来读取配置文件和注册BeanDefinition的,ClassPathBeanDefinitionScanner这个类主要就是用来扫描需要被扫描的类
ClassPathBeanDefinitionScanner是用来扫描我们自己在外部添加需要被扫描的类,在实际扫描配置文件里配置了需要被扫描的类的时候并不会使用这个类
AnnotatedBeanDefinitionReader这个类在初始化的时候又做了一件非常重要的事情—注册了一系列的BeanDefinition
BeanDefinition是Spring在生产Bean的时候所需要的,我们称之为Bean定义,Spring将扫描出来的类封装成一个BeanDefinition,这个BeanDefinition包含了一些类的基本信息,Spring再通过BeanDefinition这里面的信息来生产Bean,我们可以把这个BeanDefinition理解为是一张房屋建筑图纸,房屋怎么去做就根据这张图纸来
我们通过AnnotatedBeanDefinitionReader这个类的构造方法进入到这个方法里就可以看到它注册了很多的BeanDefinition,这些个初始化注册的BeanDefinition,我们称之为bean的后置处理器,这些个Bean的后置处理器在Spring创建Bean的时候会去调用,来完成整个Bean的创建,也就是说没有这些个后置处理器,Bean的创建就完成不了
Spring为了很好的进行解耦以及可维护性,提供了BeanFactoryPostProcessor和BeanPostProcessor这两个接口,我们统称为Bean的后置处理器
我们可以具体来看看这些个初始化注册的后置处理器
后置处理器 | 对应的作用 |
---|---|
ConfigurationClassPostProcessor | 实现了BeanFactoryPostProcessor接口,在这个类中,会解析加了@Configuration的配置类,还会解析@ComponentScan、@ComponentScans注解扫描的包,以及解析@Import等注解。 |
AutowiredAnnotationBeanPostProcessor | 实现了BeanPostProcessor接口, 用于解析@Autowired |
CommonAnnotationBeanPostProcessor | 实现了BeanPostProcessor接口 ,负责解析@Resource、@WebServiceRef、@EJB三个注解。这三个注解都是定义在javax.*包下的注解,属于java中的注解。 |
PersistenceAnnotationBeanPostProcessor | 实现了BeanPostProcessor接口 ,负责解析@PersistenceUnit、@PersistenceContext这两个注解 |
EventListenerMethodProcessor | 实现了BeanFactoryPostProcessor接口,负责解析@EventListener注解 |
DefaultEventListenerFactory | 实现了EventListenerFactory 接口,负责解析@EventListener注解 |
那么在这里比较重要的一个后置处理器是ConfigurationClassPostProcessor ,我们之前配置的javaConfig配置文件就是通过它来解析的
我们上面的步骤说了,注册BeanDefinition是需要在解析配置之后再去创建的,但为什么这里初始化的时候就直接去创建了呢
在整个IOC加载的过程中需要这些处理器来加载资源的,比如解析javaConfig配置文件,解析各种注解,如果没有这些个处理器那么配置文件就不会去加载,我们使用的这些个注解也不会去解析,那整个IOC加载就完成不了,因此这里在初始化的时候就会去注册这些BeanDefinition
那么AnnotatedBeanDefinitionReader初始化完了,紧接着就会初始化ClassPathBeanDefinitionScanner这个类,这个类主要就是用来扫描我们通过ApplicationContext设置的扫描包
我们在使用使用ApplicationContext的时候,可以自己手动去设置一个具体的扫描包
package com.ioc;
import com.ioc.config.IOCConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IOCDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(IOCConfig.class);
// 手动需要设置一个需要扫描的包
context.scan("com.ioc");
}
}
那么我们手动设置的这个包就是用过ClassPathBeanDefinitionScanner类来扫描的
在初始化完成这些类之后,接着就是将javaConfig配置类注册成BeanDefinition,以便后面的解析
为什么这里也注册了BeanDefinition呢
因为Spring所有的资源加载是通过javaConfig配置类来进行的,所以这里需要将配置类注册成一个BeanDefinition
那么我们可以再一次点击进去看下
这里注册的时候是直接通过初始化后的AnnotatedBeanDefinitionReader来注册的,以后所有的类注册包括我们扫描进来的类注册都是通过这个类来注册
解析配置文件
将配置类注册完成之后就会调用refresh()方法,这个方法就是真正来开始加载我们的IOC流程了
这个方法里面有调用了很多的方法,我们可以具体来看看
调用的方法 | 作用 |
---|---|
prepareRefresh() | 声明早期的监听器和事件 |
prepareBeanFactory() | 对Bean工厂进行填充 |
postProcessBeanFactory() | 调用postProcessBeanFactory后置处理器 |
invokeBeanFactoryPostProcessors() | 调用了bean工厂的后置处理器 ,javaConfig配置文件会在这里面解析 |
registerBeanPostProcessors() | 注册bean的后置处理器 |
initMessageSource() | 初始化国际资源 |
initApplicationEventMulticaster() | 创建事件多播器 |
onRefresh() | 初始化一些其他特殊的Bean |
registerListeners() | 把事件监听器注册到多播器上 |
finishBeanFactoryInitialization() | 创建Bean |
finishRefresh() | 容器刷新 发布刷新事件 |
那么在解析配置文件的时候主要就是调用了invokeBeanFactoryPostProcessors()这个方法,那这个方法又是怎么去解析的呢
在上面说过了,在ApplicatContext初始化的时候注册了一个ConfigurationClassPostProcessor类的BeanDefinition,而这个类就是实现了BeanFactoryPostProcessor这个接口里的postProcessBeanFactory()方法和BeanDefinitionRegistryPostProcessor这个接口里的这个postProcessBeanDefinitionRegistry()方法,那么在invokeBeanFactoryPostProcessors()这个方法里,Spring会去调用ConfigurationClassPostProcessor里的postProcessBeanFactory()方法和postProcessBeanDefinitionRegistry(),而javaConfig配置类的解析就是在这个postProcessBeanDefinitionRegistry()方法里面解析的
那我们具体来看看源码是怎么回事,我们可以进入到invokeBeanFactoryPostProcessors()这个方法里可以看到又调用了这个方法
我们可以继续点进入到这个方法里
那进入到这个方法里后,首先会去判断传入进来的这个beanFactory参数是不是实现了BeanDefinitionRegistry接口,如果实现了的话会进一步的去解析
传进来的这个参数实际上是ApplicationContext在初始化的时候就创建的一个DefaultListableBeanFactory类对象,这个类是实际上是实现了BeanDefinitionRegistry的,因此会继续往下去解析,在解析的整个过程中会调用很多次BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor,BeanDefinitionRegistryPostProcessor继承了BeanFactoryPostProcessor,具体有哪几次,我们看看下图
在解析配置文件的时候会发生在第一次调用BeanDefinitionRegistryPostProcessor的时候,我们找到第一次调用BeanDefinitionRegistryPostProcessor时候的代码
我们可以继续点击进入到代码里
可以看到代码里在调用实现了BeanDefinitionRegistryPostProcessor类里postProcessBeanDefinitionRegistry()方法,我们找到ConfigurationClassPostProcessor这个类进入到这个实现方法里去
在这个方法里最终调用了processConfigBeanDefinitions()方法去解析
我们继续进入到这个方法的内部找到这个方法
再一次进入到这个当方法里,通过方法之间的调用最终来到这个方法
通过doProcessConfigurationClass()方法开始解析配置文件
这里就是开始开始解析配置文件了
不得不说这Sring的调用链路也是比较的深,所以不要去细看每一行代码,找主流程就可以了
这里面按照顺序开始解析每一个注解
在解析配置文件的时候,Spring会根据@ComponentScans这个注解里设置的扫描的包路径来获取类进而来创建对应的BeanDefinition
我们来看看具体是怎么扫描创建的
在获取到扫描的包路径的时候,会通过调用parse()方法来获取有效的BeanDefinition
何为有效的BeanDefinition呢,我们都知道想要Spring对某个类创建Bean,得需要在类上加一个@Component或@Service注解,这里Spring在扫描的时候会将所有的类扫描进来然后再判断类上是否有注解,如果有注解才会去注册BeanDefinition,称之为有效的BeanDefinition
我们进入到这个方法里,可以看到它先是创建了一个ClassPathBeanDefinitionScanner对象
然后再去获取包路径
经过一系列的判断设置属性之后在这个方法的最后就会调用ClassPathBeanDefinitionScanner的doScan()方法
doScan()方法里会调用findCandidateComponents()方法会扫描包路径下所有的类并判断类是否有注解,如果有的话就封装成一个BeanDefinition
封装完之后就开始注册了,这里会调用registerBeanDefinition()方法将BeanDefinition注册到Map中
这里会循环已经封装好了的BeanDefinition,然后再一个一个注册到Map中去
我们上面讲的注册BeanDefinition其实就是将BeanDefinition存放到一个Map中
到这里整个配置文件就解析完了
那么注册完BeanDefinition之后,再来看看如何创建Bean的
创建Bean
Spring在调用完invokeBeanFactoryPostProcessors()之后,接着就会调用finishBeanFactoryInitialization()来创建Bean
我们继续进入到这个方法里可以看preInstantiateSingletons()这个方法
最终会调用这个方法来创建Bean
我们继续进入到这个方法里
可以看到他这里开始循环所有的beanDefinitionNames,这里的beanDefinitionNames是在创建BeanDefinition的时候就会保存起来
然后再根据beanName来获取BeanDefinition,然后再来创建Bean
看到这个getBean()方发是不是很熟悉呢,我们通过ApplicationContext获取Bean的时候是通过这个方法,那它创建Bean也是通过这个方法
那我们可以进入到这个方法里面,最终来到这个doGetBean()方法
再这个doGetBean()方法里面会根据传入进来的baenName来获取BeanDefinition,然后再把BeanDefinition转换成一个RootBeanDefinition
这为什么要转换为RootBeanDefinition呢,因为Spring通过包扫描出来创建的BeanDefinition和通过其他的途径创建出来的BeanDefinition是不一样的,因此为了后面能够更好的做判断,就统一转换成了RootBeanDefinition
再经过一系列的判断之后,最终会来到这个createBean()方法
此时注意这里的的传参,一个是beanName,一个是转换好了的RootBeanDefinition
当我们再一次进入到这个方法里面去可以看到它会调用doCreateBean()方法
那这个方法里面会通过一个createBeanInstance()方法来创建实例
Bean实例化完成之后会会对Bean里面的属性进行赋值
属性赋值完成之后,Sring会将Bean的实例存放到一级缓存中去
我们可以回到原理的那个位置找到getSingleton()方法
在这个方法里面会将创建好的Bean存放到一级缓存中去