SpringBean的生命周期,以及怎么记忆
为什么要了解生命周期
今天在写代码的时候遇到的问题:
- 在写代码的时候不清楚
@PostConstruct
和实现了InitializingBean
的方法谁先执行,导致不能确定代码顺序到底怎么写。 - 写代码的时候不清楚
实现了BeanNameAware
和init-method
方法谁先执行,导致不能确定代码顺序怎么写。
所以我痛定思痛,先要搞清楚这些生命周期扩展点的执行顺序,才能码出效率。
Demo示例
源代码
-
创建Spring简单的SpringBoot工程。使用IDE自带的SpringInitialer可以轻松创建。初始化的目录只有一个启动类。
-
编写一个普通的Bean对象,但要他实现这么几个特殊的接口
BeanNameAware BeanFactoryAware InitializingBean DisposableBean
。BeanNameAware
感知beanName,实现该接口的bean可以通过接口的setBeanName(String name)
方法拿到bean的名称BeanFactoryAware
感知BeanFactoryAware,实现该接口的bean可以通过接口的setBeanFactory(BeanFactory beanFactory)
方法拿到BeanFactory(想一下,BeanFactory是顶级容器,能拿到顶级容器意味着可以做好多事!)InitializingBean
实现该接口可以使得bean在属性注入完毕的时候进行初始化DisposableBean
实现该接口可以让bean在销毁的时候执行接口方法
pjavaackage org.t.springbean.demo; import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import org.springframework.beans.BeansException; import org.springframework.beans.factory.*; import org.springframework.stereotype.Component; @Component public class Foo implements BeanNameAware, BeanFactoryAware, InitializingBean, DisposableBean { @PostConstruct public void postConstruct() { System.out.println("bean生命周期——postConstruct初始化之前执行"); } @PreDestroy public void preDestroy() { System.out.println("bean生命周期——preDestroy bean销毁"); } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { System.out.println("bean生命周期——setBeanFactory 获取工厂的一些资源"); } @Override public void setBeanName(String name) { System.out.println("bean生命周期——setBeanName 获取bean名称:" + name); } @Override public void afterPropertiesSet() throws Exception { System.out.println("bean生命周期——afterPropertiesSet bean属性设置完之后执行"); } @Override public void destroy() throws Exception { System.out.println("bean生命周期——BeanPostProcessor执行 bean被销毁"); } }
其中还用到了两个注解@PostConstruct@PreDestroy
前者是bean创建前执行,后者是bean创建后执行
-
创建FooPostProcessor,bean的后置处理器,这个大家应该都熟悉。
package org.t.springbean.demo; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; @Component public class FooPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if (beanName.equalsIgnoreCase(Foo.class.getSimpleName())) { System.out.println("bean后置处理器——postProcessBeforeInitialization初始化之前执行"); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (beanName.equalsIgnoreCase(Foo.class.getSimpleName())) { System.out.println("bean后置处理器——postProcessAfterInitialization初始化之后执行"); } return bean; } }
执行顺序
好家伙,一个Bean初始化能干这么多事呢?而且Demo示例中的所有扩展点都是在Spring启动的时候就执行的。不需要用户自己执行任何的业务逻辑。也就是说,Spring中的一个Bean的生命周期有至少以上8种。那么我们就可以在这8种生命周期种执行我们自己的业务代码。(至少8种,比如init-method
这样的扩展点需要xml文件配置,项目中注解比较常用。这种就不说了)
执行程序前先把application.properties文件修改下配置,关闭无用的日志打印
logging.level.root=off
废话不说,直接看执行结果。
bean生命周期——setBeanName 获取bean名称:foo
bean生命周期——setBeanFactory 获取工厂的一些资源
bean后置处理器——postProcessBeforeInitialization初始化之前执行
bean生命周期——postConstruct初始化之前执行
bean生命周期——afterPropertiesSet bean属性设置完之后执行
bean后置处理器——postProcessAfterInitialization初始化之后执行
bean生命周期——preDestroy bean销毁
bean生命周期——DisposableBean执行 bean被销毁
由此可见,这些扩展点的执行顺序是:
-
BeanNameAware,
-
BeanFactoryAware,
-
Bean后置处理器的postProcessBeforeInitialization方法
-
PpostConstruct注解标注的方法
-
InitializingBean,
-
Bean后置处理器的postProcessAfterInitialization方法
-
PreDestroy注解标注的方法
-
DisposableBean
有了执行顺序之后,起始也就了解了Spring的生命周期。那么开发程序的时候就更方便了代码的顺序了。
实战使用示例
RedisTemplate设置编码
在项目中使用RedisTemplate完成对Redis的操作,但是默认的RedisTemplate存储到redis数据库种的key和value都是经过编码的,即不是明文存储。
比如set key1 value1
存储到redis种就会乱码
如果想要存储的数据是明文(不乱码)就需要对redisTemplate做一些配置操作。那么这些配置操作我写到生命周期的哪个位置合适呢?写在@PpostConstruct
注解方法种,还是写在InitializingBean接口
的方法中?它会在属性注入之前执行吗?
嘿嘿,之前不知道,现在可是清楚了SpringBean的执行时机。
使用SpringTemplate会这么使用
@Resource
private RedisTemplate redisTemplate;
我的设置是这样的
public class RedisUtil {
@Resource
private RedisTemplate redisTemplate;
@PostConstruct
public void init() {
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setStringSerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
}
}
其实实现InitializingBean
接口,写在接口方法中也是一样的,因为上面说的8中扩展点都是在Bean实例化、注入属性完成之后才会调用。
MyBatis设置拦截器
需求:给项目中添加自定义的mybatis拦截器,那么只需要属性注入后,对SqlSessionFactory
进行设置拦截器即可。
@Resource
private List<SqlSessionFactory> sqlSessionFactoryList;
@PostConstruct
public void setMybatis(){
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
sqlSessionFactory.getConfiguration().addInterceptor(new MyInterceptor());
}
}
记忆点
说实话,SpringBean的生命周期看了好多遍,但是由于不是每天都用,容易忘,所以需要一个好的理解方式让自己把它记下来。
其中对这个后置处理器就要吐槽一下了。这个后置处理器不知道是哪位大神翻译的,我一度认为既然是后置处理器,那么接口中有一个before
开头的方法是干嘛的,又成前置处理了?也不对呀。后来仔细思考了下,后置是相对的,相对于Bean的初始化,源码中的注释以及方法名可以看出,这个Bean处理器的两个方法都是before initialzation;after initialization;用英文理解下是相对于initialization这个行为的前后处理。
BeanPostProcessor:这个接口名字,本身Post有邮寄的意思,也有滞后的意思,但是这里说的滞后是相对于Bean实例是否被Spring添加到容器中去的。也就是说。后置处理器
中的后置两个字是相对于Bean实例化之后属性注入完成之后的事情。里面的方法又是另外一回事。
那么既然后置处理器的两个方法是围绕initialization这个行为进行的前后处理。那么自然就有一个initialization这么一个扩展点了。这就是@PostConstruct注解的实现意义,当然它和xml配置的init-method不是一个东西,但是实现目标是一致的,也就是对Spring容器中的Bean进行初始化。而在初始化的前后可以使用BeanPostProcessor进行插手Bean初始化的行为。
销毁,顾名思义不必说。对象使用完了才会被销毁。一般伴随着应用停止才会发生。
转载自:https://juejin.cn/post/7189957278152261689