6.引入ApplicationContext和后置处理器
🍏 6.引入
ApplicationContext
和后置处理器
在上节中,我们实现了从配置文件中读取bean的配置信息,也就是说在实例化之前增加了一个配置文件的读取和利用的模块,但资源文件读取还是要手动调用loadBeanDefinitions
方法,没有模拟出spring中真正的配置文件加载过程。所以,本节中我们继续完善spring框架,对外提供修改的接口,可以在bean 对象注册后但未实例化之前,对 Bean 的定义信息 BeanDefinition
执行修改操作。还可以在bean对象实例化之后进行修改。并且将bean对象的配置文件加载,注册,实例化,修改等操作都融合到一个上下文操作类中,本节中主要是将之前的一些操作都进行封装,对外只暴露这个上下文操作类,使其更加符合spring框架的特点。本节中涉及到的代码放在了
仓库
中
原因
在上面几节中,我们已经实现了很多的功能,包括IOC容器实现bean的实例化,创建带参的bean对象,属性填充和bean实例化分离,加载配置文件。但是到现在我们还没有提到应用上下文的概念,其实正常使用spring框架时,标准入口都是不同的“应用上下文”,所以本节中我们就将上面提到的所有功能都封装到应用上下文中,从而实现spring的进一步标准化
同时,为了在bean的生命周期中进行扩展,本节中还加入了对bean修改的接口,可以在bean实例化之前修改bean的注册信息,还可以在bean实例化之后修改bean对象,提供了两个标准接口BeanFactoryPostProcessor
和BeanPostProcessor
,实现前者的类,spring项目中就认为这是要在bean实例化之前修改bean的注册信息,实现后者的类,spring项目中就认为这是要在bean实例化之后修改bean对象,具体项目的整体结构为:
所以总结来看,提出这两个新知识点的目的在于让spring变得更加标准化,功能更加丰富,其中BeanFactoryPostProcessor是在加载完配置文件,形成所有的BeanDefinition之后触发所有的BeanFactoryPostProcessor的修改逻辑,BeanPostProcessor是在每个Bean创建完属性填充之后,然后都会触发所有的BeanFactoryPostProcessor,每个Bean都会触发一次
思路
在这里说明整体的设计思路,直接通过debug的方式来描述整个运行流程,从实例化之前的修改,到实例化,到实例化之后的修改
为了实现将整个项目包裹到应用上下文中,并且在其中bean的生命周期中加入修改的模块,需要将这两个部分分开介绍,首先是修改模块,在实例化之前的修改策略分为三步:
实现
BeanFactoryPostProcessor
接口中的postProcessBeanFactory
方法,在方法中编写自己的修改逻辑将自己的实现类以bean对象的模式加入IOC容器中
在应用上下文的构造函数中调用refresh函数,refresh中自动调用修改的逻辑:
构造函数中实现钩子函数的效果,外部使用应用上下文,就会自动调用refresh函数
1 2 3 4 5 6
public ClassPathXmlApplicationContext(String[] configLocations) throws BeansException { //得到需要加载的配置文件 this.configLocations = configLocations; //放到构造函数中, refresh(); }
refresh函数中自动调用修改的逻辑,主要修改的还是
BeanDefinition
中的属性列表,对PropertyValues
进行增删改
为了实现在bean实例化之后还可以修改bean对象,具体的步骤分为以下几步:
实现
BeanPostProcessor
接口中的postProcessBeforeInitialization
和postProcessAfterInitialization
方法,在方法中编写自己的修改逻辑,这两个方法的区别就是一个在初始化前触发,一个在初始化后触发初始化前触发的方法一般就是需要修改bean的属性以及一些元信息,需要注入容器资源的Aware回调也在这里执行,因为在这里已经直接获取不到了,在refresh方法中保存了一份修改逻辑辅助注入(在还能获取到的时候,先保存一份)
初始化后触发的方法,会在内部执行AOP,判断当前对象是否需要动态代理
两个方法存在的意义是不一样的,中间夹着初始化方法的执行
将自己的实现类以bean对象的模式加入IOC容器中
在应用上下文的构造函数中调用refresh函数,refresh中先将修改的逻辑保存到一个容器中,而不是像实例化之前修改一样直接在refresh函数中触发,这是因为此时bean还没有实例化,所以先注册,实例化之后再找到之前注册的内容,然后触发
在refresh函数中初始化所有的bean对象,按照之前介绍的流程,最终会走到createBean函数中,具体的流程如下:
在调用属性填充的
applyPropertyValues
方法之后,再增加一步initializeBean
的方法调用,在这个方法中把刚才保存到容器中的所有修改策略拿出来,依次执行:
以上两种修改策略都需要将自定义的修改策略注册为一个bean对象,也就是说BeanDefinition中保存有这些bean的注册信息,之后使用getBeansOfType方法,按照接口类型找到所有的实现类,就找到了所有的修改策略
所以为什么自定义修改策略时要实现接口,其一是为了按照类型找到自定义的修改策略,其二是为了规范修改策略的结构
实现接口之后,需要将这些自定义的修改逻辑放到配置文件中注册成bean,才能完成逻辑的触发
以上就是修改策略的实现思路,将其加入到bean的生命周期中,从refresh开始,修改模块的执行过程为:
下面介绍应用上下文的实现思路,主要是引入了一个包,在包中每一个类实现自己的功能。包括beanFactory的获取,修改策略的执行或保存,bean注册信息的读取,总之就是将上一节中的项目在外面套了一个应用上下文的壳子,然后在其中加入了修改模块,最后将之前实现的所有代码在内部封装,对外只暴露应用上下文的接口。整体的类结构为:
下面介绍项目中类的变化:
类的说明
新增的类
BeanFactoryPostProcessor
:是一个接口,实现这个接口的类被当做自定义的实例化前修改策略,具体的修改策略在内部的postProcessBeanFactory
方法中实现BeanPostProcessor
:是一个接口,实现这个接口的类被当做自定义的实例化后修改策略,具体的修改策略在内部的方法中实现
context包中新增的内容就是应用上下文的内容:
ApplicationContext
:继承ListableBeanFactory
接口,最终得到beanFactory
中的所有重点方法ConfigurableApplicationContext
:在继承ApplicationContext
的基础上增加一个待实现的refresh
方法,这是在这个方法中将之前模块以及现在自定义修改模块中的内容聚合进来,是一个核心方法,有了这个方法才实现了对外只暴露应用上下文,内部bean的生命周期正常执行AbstractApplicationContext
:继承DefaultResourceLoader,实现ConfigurableApplicationContext
接口,继承的目的是为了得到资源加载器,便于后期从配置文件中加载资源,实现的目的是为了实现refresh方法,这个类存在的意义就是为了实现refresh方法:AbstractRefreshableApplicationContext
:继承AbstractApplicationContext
类,主要是为了得到beanFactory
,同时可以将配置文件中的信息读取到:AbstractXmlApplicationContext
:继承AbstractRefreshableApplicationContext
类,在其中实现从配置文件中加载bean注册信息的功能,核心还是调用之前实现过的api来对配置文件进行加载ClassPathXmlApplicationContext
:继承AbstractXmlApplicationContext
,这是最终对外暴露的接口,主要作用是接受外部传递来的配置文件的路径,之后调用上面提到的一系列方法,最终得到实例化后的bean对象
经过新增这些类之后,整个项目的应用上下文扩展以及实例化前的修改逻辑基本完成,现在还没有完成的功能就是在实例化之后修改bean,这项功能被集成到了createBean
方法中,在这个方法中完成bean的实例化,实例化之后就可以完成实例化后修改逻辑的触发。下面被修改的类的第一个目的是为了适配以上这些修改,第二个目的是为了实现实例化之后修改bean对象
修改的类
AutowireCapableBeanFactory
:新增两个方法,用于实例化之后修改bean,具体的实现在AutowireCapableBeanFactory
类中:AbstractAutowireCapableBeanFactory
:实现了AutowireCapableBeanFactory
接口,新增了四个方法:上面几个方法的调用逻辑是在
createBean
方法中调用initializeBean
,然后在initializeBean
方法中调用其他方法:1 2 3 4 5 6 7 8 9 10 11
private Object initializeBean(String beanName, Object bean, BeanDefinition beanDefinition) { // 1. 执行 BeanPostProcessor Before 处理 Object wrappedBean = applyBeanPostProcessorsBeforeInitialization(bean, beanName); // 待完成内容:invokeInitMethods(beanName, wrappedBean, beanDefinition);,这里主要是初始化的处理 invokeInitMethods(beanName, wrappedBean, beanDefinition); // 2. 执行 BeanPostProcessor After 处理 wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); return wrappedBean; }
ConfigurableBeanFactory
:新增一个方法,目的是保存实例化后的修改策略,主要是在refresh
方法中的registerBeanPostProcessors
方法中调用,目的是为了先保存实例化后的修改逻辑,在实例化之后触发修改逻辑执行修改:1 2 3 4 5 6 7 8
private void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) { //按照BeanPostProcessor这个类型找到所有的bean Map<String, BeanPostProcessor> beanPostProcessorMap = beanFactory.getBeansOfType(BeanPostProcessor.class); for (BeanPostProcessor beanPostProcessor : beanPostProcessorMap.values()) { //保存所有的实例化后修改策略 beanFactory.addBeanPostProcessor(beanPostProcessor); } }
AbstractBeanFactory
:实现ConfigurableBeanFactory
接口,实现其中的addBeanPostProcessor
方法,将实例化后的修改策略对应的bean保存到一个容器中,之后直接拿到这个容器触发所有的实例化后修改策略:ConfigurableListableBeanFactory
:在这个接口中增加一个方法,在DefaultListableBeanFactory
类中实现,这个方法可以将完成所有的空bean的实例化,之后就可以触发实例化后的修改逻辑,在refresh方法的最后调用:DefaultListableBeanFactory
:之前项目的核心类,在这个类中新增一个实例化所有bean对象的方法,其实就是依次获取一遍所有的bean,利用getBean
方法的不存在就实例化的特点将所有的bean对象实例化,这里实例化的是空bean:1 2 3 4 5 6
@Override public void preInstantiateSingletons() throws BeansException { //遍历所有bean的注册信息,然后使用getBean的有就获取,没有就创建的思想来实例化所有没实例化的bean对象 //佯装遍历,实际上是为了实例化所有bean beanDefinitionMap.keySet().forEach(this::getBean); }
经过以上的新增和修改,就可以实现实例化后修改bean对象,并且在修改策略执行完毕之后,将所有的bean对象初始化,以上功能全部封装到了应用上下文中,整体的调用逻辑=如下图所示:
相当于现在项目中bean的生命周期变为:
- 配置文件的获取解析,主要在
refreshBeanFactory
方法中完成beanFactory
的创建以及配置文件的解析,内部将所有的正常类和实例化前后的修改逻辑类都注册成bean - 获取到上一步的
beanFactory
之后,触发实例化之前的修改逻辑,主要是按照接口类型从beanFactory
获取到所有实例化前的bean,并调用它们的方法出发实例化前的修改逻辑 - 保存实例化后的修改逻辑到一个容器中,由于此时还没有进行bean的实例化,所以先将所有的bean实例化后的修改逻辑进行保存,便于后期统一触发
- 进行bean的实例化,实例化后创建了空bean之后,完成属性填充,之后就是出发所有的实例化后修改逻辑,将之前保存实例化后修改逻辑bean的容器拿到,取出其中的bean触发修改逻辑
- 外部通过调用getBean就可以得到修改之后的bean对象,外部感知不到bean的创建过程,只是实现自己的实例化前后修改逻辑,配置好xml文件,创建一个上下文,之后就可以获取到bean对象
bean的创建和获取
经过上面的分析,项目的整体结构变得更加清晰,相比于之前的项目,程序的运行过程变得更加复杂,从应用上下文为入口,传递一个配置文件,在内部执行refresh
方法,获取到配置文件中的注册信息之后,直接执行实例化前的修改操作,并将所有的实例化后的修改操作保存到容器中,之后直接实例化,在实例化之后,再执行实例化后的修改逻辑,也就是createBean
中新增的逻辑,下面将以debug的形式来介绍bean的创建和获取的整体过程:
相当于实例化前的修改逻辑直接触发,实例化后的修改逻辑先保存后触发
初始化应用上下文,在这里传递一个配置文件的路径:
执行构造函数,在其中调用
refresh
方法:refresh方法的代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
@Override public void refresh() throws BeansException { // 1. 创建 BeanFactory,并加载 BeanDefinition //也就是加载配置文件中的内容,得到所有的bean对象,将其保存到BeanDefinition中 refreshBeanFactory(); // 2. 获取 BeanFactory ConfigurableListableBeanFactory beanFactory = getBeanFactory(); // 3. 在 Bean 实例化之前,执行 BeanFactoryPostProcessor (Invoke factory processors registered as beans in the context.) //修改bean的属性列表,相当于在实例化之前修改bean的注册信息 invokeBeanFactoryPostProcessors(beanFactory); // 4. BeanPostProcessor 需要提前于其他 Bean 对象实例化之前执行注册操作 //将实例化之后的修改策略保存住 registerBeanPostProcessors(beanFactory); // 5. 提前实例化单例Bean对象 //实例化所有的对象之后,就可以实现 beanFactory.preInstantiateSingletons(); }
在这里集成了之前所有的模块并加入了实例化前后的修改模块
refresh
函数中调用refreshBeanFactory
方法,目的是为了获取到beanFactory
对象,为了后期bean的注册和实例化做准备,这里的beanFactory已经加载了配置文件:加载配置文件,直接利用上一节中创建的配置文件加载方法来加载给定的配置文件,只是资源加载器的创建以及配置文件解析的方法在内部直接调用,外部看不到了:
获取创建好的
beanFactory
对象,里面已经有加载好的bean注册信息,所有的bean都还没有被初始化:执行实例化前的修改逻辑,主要是修改
BeanDefinition
中的PropertyValues
,通过类型获取到自定义的修改逻辑之后,直接执行修改逻辑:自定义的修改逻辑为:
1 2 3 4 5 6 7 8 9 10 11
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { BeanDefinition beanDefinition = beanFactory.getBeanDefinition("userService"); PropertyValues propertyValues = beanDefinition.getPropertyValues(); //增加一个字段 propertyValues.addPropertyValue(new PropertyValue("company", "改为:字节跳动")); } }
这里的实例化前修改是直接添加了一个属性字段,相当于现在有两个重复的
company
,但是新添加的最新的属性字段在后面,属性填充时会覆盖旧的,所以展示出来的就是最新的属性,这里更加优雅的方法就是遍历属性列表,找到要修改的直接修改,而不是新增一个属性用新的覆盖旧的,之所以会覆盖是因为属性列表是一个ArrayList
,新增的属性在后面才可以覆盖保存实例化后的修改逻辑,还是按照类型获取到修改逻辑,注意此时获取到之后只是简单的保存,保存到容器之后,在实例化和属性填充之后统一触发:
保存的具体逻辑,也就是
addBeanPostProcessor
,将其保存到一个容器中:执行完实例化前修改的逻辑以及保存完实例化后修改的逻辑之后,这两个自定义的修改策略已经被保存到了IOC容器中,因为他们内部都调用了
getBeansOfType
方法,内部调用了getBean
方法,使得这两个bean被提前实例化,但是这两个bean时给bean用的,而不是给用户用的前期准备工作完成后,实例化所有的bean对象,利用
getBean
方法来遍历所有已注册的bean,将其实例化,最终会调用到createBean
方法中:到达
createBean
方法中之后,每一个空bean创建之后,就是属性填充,此时就会用到实例化之前修改的bean注册信息实例化之后的修改,在
initializeBean
方法中执行,在这个方法中调用修改逻辑,每一个bean走到这一步都会执行这个方法:修改逻辑的调用,从容器中拿到所有待执行的修改逻辑,然后依次执行,就是实现了对bean对象的修改,注意此时是每一个bean都会触发实例化后的修改逻辑,但是这个修改逻辑如果不是针对当前bean的,那么就不做任何修改,相当于每个bean都去判断当前修改逻辑是不是针对自己的,是的话就修改,不是就什么都不做:
postProcessBeforeInitialization
方法内部的逻辑为,相当于当前修改逻辑是针对userService的:1 2 3 4 5 6 7 8
@Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if ("userService".equals(beanName)) { UserService userService = (UserService) bean; userService.setLocation("改为:北京"); } return bean; }
如果修改逻辑是针对当前bean的,那么将修改之后的实例化对象保存到IOC容器中并返回,此时bean对象的信息修改为,如果不是针对当前bean的,那么就什么都不做:
经历以上这些步骤,会将待修改的bean进行修改,不修改的bean也进行实例化,此时IOC容器中保存了所有实例化后的bean对象,并且修改的操作也执行完成,后期想要使用这些bean对象时,直接getBean
即可:
总结
本节在之前的基础上实现了应用上下文,并且增加了修改模块,实现实例化前后都可以修改bean,为了实现应用上下文,将上一节中的DefaultListableBeanFactory
包装到了context
包的类中,对外暴露的不再是这个类,而是ClassPathXmlApplicationContext
类,用户只需要提供一个配置文件,之后就可以尽情使用bean对象了
为了实现修改模块,项目将其在refresh
函数中进行聚合,实例化前的修改主要修改的是注册信息,也就是PropertyValues
中的内容(此时bean对象还不存在),实例化后的修改主要是利用set
方法来修改bean对象中的内容(此时bean对象已经存在了),在refresh
函数中,实例化前的修改操作直接执行,实例化后的操作先保存到一个容器中,在createBean
函数的空bean创建,属性填充(利用实例化前已经修改过的属性)之后再加入一步,将实例化后的bean进行修改,每一个bean都会判断当前所有的实例化后修改逻辑是不是针对自己的
以上两个模块的结构图如下图所示,核心的地方用红框标注,refresh
中聚合所有的修改模块并在其中执行实例化前的修改,createBean
中新增一个initializeBean
方法,执行实例化之后的修改,最后将ClassPathXmlApplicationContext
暴露给用户使用: