对SpringBean生命周期的理解
1、为什么要理解Bean的生命周期https://blog.csdn.net/yjc0403/article/details/84831832Bean的生命周期可以理解为Bean从产生到销毁的过程,中间涉及到了实例化、属性注入、初始化等流程,同时后置处理器BeanPostprocessor穿插执行,只有了解了这个过程,才能在Bean生产过程中进行一些定制化操作。以beanPostProcessor
1、为什么要理解Bean的生命周期
https://blog.csdn.net/yjc0403/article/details/84831832
Bean的生命周期可以理解为Bean从产生到销毁的过程,中间涉及到了实例化、属性注入、初始化等流程,同时后置处理器BeanPostprocessor
穿插执行,只有了解了这个过程,才能在Bean生产过程中进行一些定制化操作。
以beanPostProcessor为例:
背景
公司有一个线上数据源加密包, 这个包是在原开源包基础上修改了源码把明文的password设置了密文, 随着时间推移这个包没人维护,配置产生了各种问题, 很多项目时间浪费在了配置这个数据源上面。 我在搭建一个新项目就遇到了这样的问题。
问题
我不想使用这个陈旧的被修改了源码的数据源,那么面临的问题是:
开源数据源包仅支持明文密码的配置, 我得需求是配置文件中的密码为密文, 在dataSource的bean加载之后动态的将配置文件密文解密为明文, 并且setPassword到DataSource。
那么问题就来了
如何在dataSource 的 bean 初始化(将配置文件中的url,password等加载到bean中)后, 在dataSource被调用前, 获取到bean的password字段 ,解密, 重新set进去?
如果了解spring生命周期的同学应该很熟悉spring的BeanPostProcessor接口, 实现了该接口的类中有个方法:
@Override
public Object postProcessAfterInitialization(Object bean, String name)
throws BeansException {
return bean;
}
解决方案
在spring容器初始化过程中, 会在bean被初始化后统一循环调用实现BeanPostProcessor接口并配置到IOC容器的bean 的 该方法。
那么在方法体获取DataSource的password , 解密, 设置明文密码就能达到开始时候的需求了。
代码:
public class DataSourcePasswordEncryptPostProcessor implements
BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String name)
throws BeansException {
// TODO Auto-generated method stub
if(bean instanceof ProxoolDataSource){
ProxoolDataSource ds = (ProxoolDataSource)bean;
String password = ds.getPassword();
PBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword("xxxxxxx");
//明文密码
String encPassword = encryptor.decrypt(password);
ds.setPassword(encPassword);
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String name)
throws BeansException {
// TODO Auto-generated method stub
return bean;
}
2、钩子方法
所谓钩子方法,可以理解为比如bean的生命周期,到某一阶段自动会调用某个方法(有点类似回调函数),这个方法就是钩子方法,我们可以通过对钩子方法进行重写进行一些定制化操作。
3、Spring中Bean的生命周期具体流程
https://zhuanlan.zhihu.com/p/159554995
Bean创建的三个阶段
Spring在创建一个Bean时是分为三个步骤的
- 实例化,可以理解为new一个对象
- 属性注入,可以理解为调用setter方法完成属性注入
- 初始化,你可以按照Spring的规则配置一些初始化的方法(例如,
@PostConstruct
注解)
生命周期的概念
Bean的生命周期指的就是在上面三个步骤中后置处理器BeanPostprocessor
穿插执行的过程
后置处理器的分析
按照实现接口进行分类
- 直接实现了
BeanPostProcessor
接口
最简单的后置处理器,也就是说直接实现了BeanPostProcessor
接口,这种后置处理器只能在初始化前后执行
public interface BeanPostProcessor {
// 初始化前执行的方法
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
// 初始化后执行的方法
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
- 直接实现了
InstantiationAwareBeanPostProcessor
接口
在第一种后置处理的基础上进行了一层扩展,可以在Bean的实例化阶段前后执行
// 继承了BeanPostProcessor,额外提供了两个方法用于在实例化前后的阶段执行
// 因为实例化后紧接着就要进行属性注入,所以这个接口中还提供了一个属性注入的方法
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
// 实例化前执行
default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
return null;
}
// 实例化后置
default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return true;
}
// 属性注入
default PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
return pvs;
}
}
- Spring内部专用的后置处理器
可能有的小伙伴认为,第三种后置处理器肯定就是用来在属性注入前后执行了的吧。我只能说,大兄弟,太天真了,看看下面这张图
这种情况下再为属性注入阶段专门提供两个方法是不是有点多余呢?实际上第三种后置处理器是Spring为了自己使用而专门设计的
public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {
// 推测bean的类型,例如在属性注入阶段我们就需要知道符合依赖类型的Bean有哪些
@Nullable
default Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
return null;
}
// 推断出所有符合要求的构造函数,在实例化对象的时候我们就需要明确到底使用哪个构造函数
@Nullable
default Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName)
throws BeansException {
return null;
}
// 获取一个提前暴露的对象,用于解决循环依赖
default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}
}
一般我们在探究生命周期的时候都不会考虑这种后置处理器的执行
生命周期详细介绍
在了解了上面的概念后,我们再来看看这张图
至少现在这张图上缺少了实例化前后后置处理器的执行流程,对吧?
再补充上这一点之后,我们再来看看,属性注入后紧接着已经是初始化的阶段,在初始化阶段开始前应该要调用BeanPostProcessor
的预初始化方法(postProcessBeforeInitialization
),然后调用自定义的初始化方法,最后调用postProcessAfterInitialization
,这是没有问题,但是为什么要在初始前还要调用Aware接口的方法,如果你看了源码的话可能会说,源码就是这么写的,别人就是这么设计的,但是为什么要这么设计呢?我们看源码到一定阶段后不应该仅仅停留在是什么的阶段,而应该多思考为什么,这样能帮助你更好的了解这个框架
那么为什么Aware接口非要在初始化前执行呢?
这样做的目的是因为,初始化可能会依赖Aware接口提供的状态,例如下面这个例子
@Component
public class A implements InitializingBean, ApplicationContextAware {
ApplicationContext applicationContext;
@Override
public void afterPropertiesSet() throws Exception {
// 初始化方法需要用到ApplicationContextAware提供的ApplicationContext
System.out.println(applicationContext);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
这种情况下Aware接口当然要在初始化前执行啦!
另外,在讨论Bean的初始化的时候经常会碰到下面这个问题,@PostConstruct
,afterPropertiesSet
跟XML中配置的init-method
方法的执行顺序。
请注意,@PostConstruct
实际上是在postProcessBeforeInitialization
方法中处理的,严格来说它不属于初始化阶段调用的方法,所以这个方法是最先调用的
其次我们思考下是调用afterPropertiesSet
方法的开销大还是执行配置文件中指定名称的初始化方法开销大呢?我们不妨用伪代码演示下
// afterPropertiesSet,强转后直接调用
((InitializingBean) bean).afterPropertiesSet()
// 反射调用init-method方法
// 第一步:找到这个方法
Method method = class.getMethod(methodName)
// 第二步:反射调用这个方法
method.invoke(bean,null)
相比而言肯定是第一种的效率高于第二种,一个只是做了一次方法调用,而另外一个要调用两次反射。
因此,afterPropertiesSet
的优先级高于XML配置的方式
所以,这三个方法的执行顺序为:
@PostConstruct
注解标注的方法- 实现了
InitializingBean
接口后复写的afterPropertiesSet
方法 - XML中自定义的初始化方法
在完成初始化,没什么好说的了,最后调用一下postProcessAfterInitialization
,整个Bean的生命周期到此结束
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)