Spring之Bean的生命周期

190 阅读7分钟

Bean的生命周期

在传统的Java应用中,bean的生命周期很简单。使用Java关键字new进行bean实例化,然后该bean就可以使用了。一旦该bean不再被使用,则由Java自动进行垃圾回收。相比之下,Spring容器中的bean的生命周期就显得相对复杂多了。正确理解Spring bean的生命周期非常重要,因为你或许要利用Spring提供的扩展点来自定义bean的创建过程。

1. 什么是Bean的生命周期

Spring可以理解为一个管理Bean对象的工厂。它负责对象的创建,对象的销毁等。所谓的生命周期就是:对象从创建开始到最终销毁的整个过程。主要就是得知道创建Bean对象的时机,创建Bean对象的前后会调用什么方法,Bean对象销毁的时机以及Bean对象的销毁前后调用了什么方法。

生命周期的本质是Spring在哪个时间节点上调用了哪个类的哪个方法。只有充分的了解了特殊的时间节点在哪,才可以确定代码写到哪,我们可能需要在某个特殊的时间点上执行一段特定的代码,这段代码就可以放到这个节点上。当生命线走到这里的时候,自然会被调用。

2. Bean的生命周期之5步

Bean生命周期的管理,可以参考Spring的源码:AbstractAutowireCapableBeanFactory类的doCreateBean()方法

Bean生命周期可以粗略的划分为五大步:

  • 第一步:实例化Bean,调用bean的构造方法
  • 第二步:Bean属性赋值,调用bean的set方法(此时只能是使用set注入)
  • 第三步:初始化Bean,需要在类里自己定义方法,然后再配置bean的时候进行指定。
  • 第四步:使用Bean,这一步就是客户端程序从Spring容器里获取bean之后进行的使用的过程。
  • 第五步:销毁Bean,同初始化一样,需要在类里自己定义方法,然后再配置bean的时候进行指定。

编写测试程序如下:

public class SpringBean { //定义一个类
    private int a;

    public SpringBean() {
        System.out.println("第一步,执行无参构造");
    }

    public void setA(int a) {
        System.out.println("第二步,给对象赋值");
        this.a = a;
    }

    public void initBean(){
        System.out.println("第三步,进行bean的初始化");
    }

    public void destroyBean(){
        System.out.println("第五步,销毁bean");
    }
}
<!--对bean进行配置,需要使用bean标签的init-method属性和destory-method方法,指定bean的初始化和销毁应该调用的方法-->
<bean id="springBean" class="com.qiuye.beanInstance.entity.SpringBean" init-method="initBean" destroy-method="destroyBean">
    <property name="a" value="1"/>
</bean>
@Test
public void test() {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-instance.xml");
    SpringBean springBean = applicationContext.getBean("springBean",SpringBean.class);
    System.out.println("第四步,使用bean"+springBean);
    applicationContext.close(); //需要关闭容器,才会执行bean的销毁方法
}

执行结果:

image.png

需要注意的:

  • 只有正常关闭spring容器,bean的销毁方法才会被调用,而且只在ClassPathXmlApplicationContext类才有close()方法。
  • 配置文件中的init-method指定初始化方法。destroy-method指定销毁方法。

3. Bean生命周期之7步

在将bean划分为五步的基础上,其中第3步是初始化Bean,如果继续细化声明周期可以在初始化前和初始化后添加代码,可以让某个类实现BeanPostProcessor类——Bean后处理器,并且重写before和after方法:

public class LogBeanPostProcess implements BeanPostProcessor {
    @Override
    /**
     * bean 该参数表示当前bean本身,此时bean已经被构造出来并注入了属性
     * beanName 在bean标签配置的bean的id
     * 简称before方法
     */
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在bean初始化之前"+ bean + ":" + beanName);
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }
    /**
     * bean 该参数表示当前bean本身,此时bean已经被构造出来并注入了属性
     * beanName 在bean标签配置的bean的id
     * 简称after方法
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在bean初始化之后");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

在spring.xml文件中配置“Bean后处理器”:

<!--配置Bean后处理器。这个后处理器将作用于当前配置文件中所有的bean。-->
<bean class="com.qiuye.beanInstance.entity.LogBeanPostProcess"/>

一定要注意:在spring.xml文件中配置的Bean后处理器将作用于当前配置文件中所有的Bean。,这个可以去测试一下,也可以看看在其他配置文件里面配置bean后处理器会不会影响当前配置文件里面的类。

执行测试程序:

image.png

如果加上Bean后处理器的话,Bean的生命周期就是7步了,在这个处理器里面可以对bean初始化前后做一些统一的处理。

4. Bean生命周期之10步

如果根据源码跟踪,可以划分更细粒度的步骤,可以分为十步,与七步相比多的三步是在检查是否实现了相关接口及其对应的方法,主要是三种接口:

  • Aware相关的接口包括:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware。在Bean后处理器的before方法执行之前检查。

当Bean实现了BeanNameAware,Spring会将Bean的名字传递给Bean。
当Bean实现了BeanClassLoaderAware,Spring会将加载该Bean的类加载器传递给Bean。
当Bean实现了BeanFactoryAware,Spring会将Bean工厂对象传递给Bean。

  • InitializingBean,在Bean后处理器的before方法执行之后检查并执行afterPropertiesSet方法。
  • DisposableBean,在销毁bean之前检查并执行destroy方法。

测试以上10步,可以让User类实现5个接口,并实现所有方,在七步的基础上进行修改,主要是SpringBean类,代码如下:

public class SpringBean implements BeanNameAware, BeanFactoryAware, BeanClassLoaderAware, InitializingBean, DisposableBean {
    private int a;

    public SpringBean() {
        System.out.println("1,执行无参构造");
    }

    public void setA(int a) {
        System.out.println("2,给对象赋值");
        this.a = a;
    }

    public void initBean(){
        System.out.println("6,进行bean的初始化");
    }

    public void destroyBean(){
        System.out.println("10,销毁bean");
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("3,(BeanClassLoaderAware)类加载器:" + classLoader);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("3,(BeanFactoryAware)Bean工厂:" + beanFactory);
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("3,(BeanNameAware)bean的名字:"+ name);
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("9,(DisposableBean)的destroy执行");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("5,(InitializingBean)的afterPropertiesSet执行");
    }
}

执行结果:

image.png 通过测试可以看出来:

  • InitializingBean的方法早于init-method的执行。
  • DisposableBean的方法早于destroy-method的执行。

总结下来就是:

  • 1.Spring对bean进行实例化;
  • 2.Spring将值和bean的引用注入到bean对应的属性中;
  • 3.如果bean实现了BeanNameAware接口,Spring将bean的ID传递给 setBeanName()方法;
  • 3.如果bean实现了BeanClassLoaderAware接口,Spring将调用setBeanClassLoader()方法,将bean所在的类加载器引用传入进来;
  • 3.如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
  • 4.如果bean实现了BeanPostProcessor接口,Spring将调用它的postProcessBeforeInitialization()方法;
  • 5.如果bean实现了InitializingBean接口,Spring将调用它们的 afterPropertiesSet()方法。
  • 6.如果bean使用initmethod声明了初始化方法,该方法也会被调用;
  • 7.如果bean实现了BeanPostProcessor接口,Spring将调用它们 的postProcessAfterInitialization()方法;
  • 8.此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
  • 9.如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样
  • 10.如果bean使用destroy-method声明 了销毁方法,该方法也会被调用。

Bean生命周期.png

5. Bean的作用域不同,管理方式不同

Spring 根据Bean的作用域来选择管理方式。

  • 对于singleton作用域的Bean,Spring 能够精确地知道该Bean何时被创建,何时初始化完成,以及何时被销毁;
  • 而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。

之前的SpringBean类是在默认作用域也就是singleton的情况下进行测试的,现在需要把spring.xml文件中的配置scope设置为prototype,其他的保持不变:

<bean id="springBean" class="com.qiuye.beanInstance.entity.SpringBean" scope="prototype" init-method="initBean" destroy-method="destroyBean">
    <property name="a" value="1"/>
</bean>

执行测试程序:

image.png

通过测试一目了然。只执行了前8步,第9和10都没有执行。

6. 自己new的对象如何让Spring管理

有些时候可能会遇到这样的需求,某个java对象是我们自己new的,然后我们希望这个对象被Spring容器管理,应该怎么处理?直接这样测试,将SpringBean从配置文件里取消配置,然后编写测试程序如下:

    public void test() {
        SpringBean springBean = new SpringBean();
        System.out.println(springBean);
        //DefaultListableBeanFactory类可以向Spring注册自己new的实例
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        factory.registerSingleton("springBean", springBean);
        SpringBean bean = factory.getBean("springBean", SpringBean.class);
        System.out.println(bean);
    }

执行结果: image.png 从执行结果来看,实例化的对象确实被放在了Spring容器里,但是之后并没有对其的生命周期进行管理,这个还得看看源码才知道其中的缘由,简单地看了一下是将其放在了 DefaultSingletonBeanRegistry的一级缓存(singletonObjects)里,而不是放在DefaultListableBeanFactory的beanDefinitionMap里。