SpringBean的生命周期,以及怎么记忆

1,583 阅读6分钟

为什么要了解生命周期

今天在写代码的时候遇到的问题:

  1. 在写代码的时候不清楚@PostConstruct和实现了InitializingBean的方法谁先执行,导致不能确定代码顺序到底怎么写。
  2. 写代码的时候不清楚实现了BeanNameAwareinit-method方法谁先执行,导致不能确定代码顺序怎么写。

所以我痛定思痛,先要搞清楚这些生命周期扩展点的执行顺序,才能码出效率。

Demo示例

源代码

  1. 创建Spring简单的SpringBoot工程。使用IDE自带的SpringInitialer可以轻松创建。初始化的目录只有一个启动类。

  2. 编写一个普通的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创建后执行

  1. 创建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被销毁

由此可见,这些扩展点的执行顺序是:

  1. BeanNameAware,

  2. BeanFactoryAware,

  3. Bean后置处理器的postProcessBeforeInitialization方法

  4. PpostConstruct注解标注的方法

  5. InitializingBean,

  6. Bean后置处理器的postProcessAfterInitialization方法

  7. PreDestroy注解标注的方法

  8. 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初始化的行为。

销毁,顾名思义不必说。对象使用完了才会被销毁。一般伴随着应用停止才会发生。