[译]Spring Bean的生命周期

原文:Spring Bean Lifecycle


Spring IoC(Inversion of Control,控制反转)容器管理着Spring中的bean。一个"Spring bean"只是一个由Spring管理的Java类的实例。
Spring的IoC容器负责实例化、初始化并写入bean,同时也管理这些bean的生命周期。
Spring提供了一些方法,你可以通过它们来进入bean的生命周期。 例如,一旦一个bean被实例化,你可能就需要执行一些初始化操作来使这个bean进入可用状态。类似地,你可能需要在一个bean被从容器移除之前进行资源的清理。
在这篇文章中,我们会研究Spring bean生命周期的各个阶段。这就是Spring框架创建和销毁Spring bean的方式。

Spring Bean 生命周期概览

下面的图片展示了Spring bean生命周期的两个部分:

image

Part 1:展示了一个bean在实例化之后到准备就绪可用前所经历的不同阶段。
Part 2:展示了当一个Spring Ioc容器关闭,一个bean会经历些什么。
正如你在前面图片的Part 1中所看见的,容器通过调用它的构造器来实例化一个bean然后填充它的属性。
之后对bean进行多次调用,直到bean处于就绪状态。
类似地,像Part 2中展示的那样,当容器关闭,容器调用bean来使其能够在销毁bean之前执行所有需要的任务。

Aware接口

Spring提供了一些aware接口,他们用来进入Spring框架的基础层。aware接口主要在框架中使用,很少被Spring程序员使用。
作为一个Spring程序员应该熟悉下面三个aware接口:

  • BeanFactoryAware:提供setBeanFactory(),一个提供bean实例所属工厂的回调。
  • BeanNameAware: 此接口的ThesetBeanName()回调提供了bean的名称。
  • ApplicationContextAware:此接口的ThesetApplicationContext()方法提供了此bean的ApplicationContext对象
    使用上述aware接口的代码如下:
package guru.springframework.springbeanlifecycle.awareinterfaces.domain;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import java.util.Arrays;
public class AwareBeanImpl implements ApplicationContextAware, BeanNameAware, BeanFactoryAware {
  @Override
  public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    System.out.println("setBeanFactory method of AwareBeanImpl is called");
    System.out.println("setBeanFactory:: AwareBeanImpl singleton= "
    + beanFactory.isSingleton("awareBean"));
  }
  @Override
  public void setBeanName(String beanName) {
    System.out.println("setBeanName method of AwareBeanImpl is called");
    System.out.println("setBeanName:: Bean Name defined in context= "
    + beanName);
  }
  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    System.out.println("setApplicationContext method of AwareBeanImpl is called");
    System.out.println("setApplicationContext:: Bean Definition Names= "
    + Arrays.toString(applicationContext.getBeanDefinitionNames()));
  }
}

前面的bean实现了ApplicationContextAwareBeanNameAwareBeanFactoryAware接口。
代码行13-18:代码重写了BeanFactoryAware接口的setBeanFactory()方法。在运行期间,Spring传递创建bean的BeanFactory对象。该代码使用这个BeanFactory对象来打印这个bean是否是一个单例。
代码行20-25:重写了BeanNameAware接口的setBeanName()方法。在运行期间,Spring以一个String来传递这个bean的名字,被该代码打印了出来。代码使用了beanName来打印上下文中定义的bean的名字。
代码行27-32:重写了ApplicationContextAware接口的setApplicationContext()方法,在运行期间,Spring传递创建了这个bean的ApplicationContext对象。代码用这个ApplicationContext对象来打印bean定义的名字。
接下来,我们将编写bean的配置来定义AwareBeanImpl。beans.xml的代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
   <!--    awareinterfaces-->
   <bean id="awareBean" class="guru.springframework.springbeanlifecycle.awareinterfaces.domain.AwareBeanImpl">
   </bean>
</beans>

最后,让我们编写主类,它将加载bean .xml并测试相关接口方法:

package guru.springframework.springbeanlifecycle;
import guru.springframework.springbeanlifecycle.awareinterfaces.domain.AwareBeanImpl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@SpringBootApplication
public class SpringBeanLifecycleApplication {
  public static void main(String[] args) {
    SpringApplication.run(SpringBeanLifecycleApplication.class, args);
    // -------awareinterfaces---------
    ApplicationContext context1 =
    new ClassPathXmlApplicationContext("beans.xml");
    AwareBeanImpl awareBeanImpl = (AwareBeanImpl) context1.getBean("awareBean");
    ((AbstractApplicationContext) context1).registerShutdownHook();
  }
}

运行主类的输入如下:

setBeanName method of AwareBeanImpl is called
setBeanName:: Bean Name defined in context= awareBean
setBeanFactory method of AwareBeanImpl is called
setBeanFactory:: AwareBeanImpl singleton= true
setApplicationContext method of AwareBeanImpl is called
setApplicationContext:: Bean Definition Names= [awareBean]

Bean后置处理

Spring提供了BeanPostProcessor接口,为你提供了进入Spring上下文生命周期和在处理bean时和他们交互的方法。
BeanPostProcessor接口包含两个方法:

  • postProcessBeforeInitialization:Spring在调用aware接口的方法之后和任何bean的初始化回调之前调用此方法,例如InitializingBeanafterPropertiesSet()或者任何自定义的初始化方法。
  • postProcessAfterInitialization:Spring在任何bean的初始化回调之后调用此方法。

让我们从创建一个叫BookName的bean开始:

package guru.springframework.springbeanlifecycle.beanpostprocessor.domain;
public class BookBean {
    private String bookName;
    public BookBean() {
        System.out.println("Constructor of BookBean called !! ");
    }
    public BookBean(String bookName) {
        this.bookName = bookName;
    }
    public String getBookName() {
        return bookName;
    }
    public void setBookName(String bookName) {
        this.bookName = bookName;
    }
    @Override
    public String toString() {
        return "BookBean{" +
        "bookName='" + bookName + '\'' +
        '}';
    }
}

接下来,我们创建一个BookBeanPostProcessor
BookBeanPostProcessor代码如下:

package guru.springframework.springbeanlifecycle.beanpostprocessor.domain;
import guru.springframework.springbeanlifecycle.beanpostprocessor.domain.BookBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class BookBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Post Process Before Initialization method is called : Bean Name " + beanName);
        return bean; 
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Post Process After Initialization method is called : Bean Name " + beanName);
        return bean;
    }
}

上述代码实现了BeanPostProcessor接口并覆盖了postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法。
Spring在调用了aware接口的方法之后。调用postProcessBeforeInitialization()方法。
Spring在所有的bean初始化回调之后调用postProcessAfterInitialization()方法,例如InitializingBean的afterPropertiesSet()或者任何自定义的初始化方法。我们接下来两个都会讨论。
在运行时,Spring将向这两个方法注入新的bean实例和bean名称。
接下来,我们会在XML配置中定义BookBeanBookBeanProcessor作为bean。
配置代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="bookBeanPost" class="guru.springframework.springbeanlifecycle.beanpostprocessor.domain.BookBean">
<property name="bookName" value="Gone with the Wind"></property>
</bean>
<bean id="bookBeanPostProcessor"
class="guru.springframework.springbeanlifecycle.beanpostprocessor.domain.BookBeanPostProcessor"/>
</beans>

测试我们的BeanPostProcessor的代码如下:

package guru.springframework.springbeanlifecycle;
import guru.springframework.springbeanlifecycle.beanpostprocessor.domain.BookBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@SpringBootApplication
public class SpringBeanLifecycleApplication {
public static void main(String[] args) {
    SpringApplication.run(SpringBeanLifecycleApplication.class, args);
    // -------beanpostprocessor------
    ApplicationContext context4 =
    new ClassPathXmlApplicationContext("beans.xml");
    BookBean bookBean = (BookBean) context4.getBean("bookBeanPost");
    ((AbstractApplicationContext) context4).registerShutdownHook();
    }
}

输出如下:

  • Constructor of BookBean called !!
  • Post Process After Initialization method is called: Bean Name bookBeanPost
  • Post Process Before Initialization method is called: Bean Name bookBeanPost

InitializingBean和DisposableBean回调接口

Spring提供下列两个回调接口:

  • InitializingBean:声明了afterPropertiesSet()方法,能够用来编写初始化逻辑。容器在属性设置完成后调用这个方法。
  • DisposableBean:声明了destroy()方法,能够用来编写清理代码。容器在关闭的bean销毁期间调用此方法
    让我们来编写一个实现了InitializingBean和DisposableBean接口的bean。
    bean Book的代码如下:
package guru.springframework.springbeanlifecycle.callbackinterfaces.domain;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class Book implements InitializingBean, DisposableBean {
    private String bookName;
    public Book() {
        System.out.println("Constructor of Book bean is called !! ");
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("Destroy method of Book bean called !! ");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet method of Book bean is called !! ");
    }
    public Book(String bookName) {
        this.bookName = bookName;
    }
    public String getBookName() {
        eturn bookName;
    }
    public void setBookName(String bookName) {
        this.bookName = bookName;
    }
    @Override
    public String toString() {
        return "Book{" +
        "bookName='" + bookName + '\'' +
        '}';
    }
}

前面的Book bean实现了InitializingBeanDisposableBean接口,并覆盖了它们的afterPropertiesSet()和destroy()方法。
接下来,我们要编写bean配置来定义Book的bean。
beans.xml的代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- callbackinterfaces-->
<bean id="bookBean" class="guru.springframework.springbeanlifecycle.callbackinterfaces.domain.Book">
<property name="bookName" value="Believe in Yourself"/>
</bean>
</beans>

main类如下

package guru.springframework.springbeanlifecycle;
import guru.springframework.springbeanlifecycle.callbackinterfaces.domain.Book;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@SpringBootApplication
public class SpringBeanLifecycleApplication {
public static void main(String[] args) {
    SpringApplication.run(SpringBeanLifecycleApplication.class, args);
    // -------callbackinterfaces-------
    ApplicationContext context =
    new ClassPathXmlApplicationContext("beans.xml");
    Book book = (Book) context.getBean("bookBean");
    System.out.println(book.getBookName());
    ((AbstractApplicationContext) context).registerShutdownHook();
    }
}

前面的代码从ApplicationContext中取出了Book的bean,并打印出了bookName属性。
运行主类的输出如下:

  • The constructor of Book bean is called !!
  • afterPropertiesSet method of Book bean is called !!
  • Believe in Yourself
  • destroy method of Book bean is called !!

正如您在输出中所注意到的,afterPropertiesSet()方法首先被调用。

自定义初始化和销毁方法

当在XML配置中声明bean时,你可以在tag中指定init-method和destroy-method属性。这两个属性都指定了bean类中的自定义方法。
init-method属性中声明的方法是在Spring通过setter或构造函数参数初始化bean属性之后调用的。您可以使用此方法来验证注入的属性或执行任何其他任务。
Spring在销毁bean之前调用destroy-method属性中声明的方法。
让我们使用一个名为BookCustomBean的bean中的自定义init和destroy方法。
BookCustomBean的代码如下:

package guru.springframework.springbeanlifecycle.custominitanddestroy.domain;
public class BookCustomBean {
    private String bookName;
    public BookCustomBean() {
        System.out.println("Constructor of BookCustomBean bean is called !! ");
    }
    public void customDestroy() throws Exception {
        System.out.println("Custom destroy method of BookCustomBean called !! ");
    }
    public void customInit() throws Exception {
        System.out.println("Custom Init method of BookCustomBean called !! ");
    }
    public BookCustomBean(String bookName) {
        this.bookName = bookName;
    }
    public String getBookName() {
        return bookName;
    }
    public void setBookName(String bookName) {
        this.bookName = bookName;
    }
    @Override
    public String toString() {
        return "Book{" +
        "bookName='" + bookName + '\'' +
        '}';
    }
}

在前面的代码中,customInit和customDestroy是打印输出消息的常规方法。
接下来,我们将编写bean的配置,bean.xml。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- Declare custom init and destroy methods-->
<bean id="customLifeCycleBookBean"
class="guru.springframework.springbeanlifecycle.custominitanddestroy.domain.BookCustomBean"
init-method="customInit"
destroy-method="customDestroy">
<property name="bookName" value="Life and Laughing"></property>
</bean>
</beans>

在前面的代码中,第11行-第12行使用值customInit和customDestroy的init-method和destroy-method属性。

main类的代码如下:

package guru.springframework.springbeanlifecycle;
import guru.springframework.springbeanlifecycle.custominitanddestroy.domain.BookCustomBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@SpringBootApplication
public class SpringBeanLifecycleApplication {
    public static void main(String[] args) {
    SpringApplication.run(SpringBeanLifecycleApplication.class, args);
    // -------custominitanddestroy------
    ApplicationContext context3 =
    new ClassPathXmlApplicationContext("beans.xml");
    BookCustomBean bookCustomBean = (BookCustomBean) context3.getBean("customLifeCycleBookBean");
    ((AbstractApplicationContext) context3).registerShutdownHook();
    }
}

前面的代码加载XML配置并测试init-method和destroy-method。
运行代码输出如下:

  • Constructor of BookCustomBean bean is called !!
  • Custom Init method of BookCustomBean called !!
  • Custom destroy method of BookCustomBean called !!

总结

所有Spring bean都要经历一个特定的生命周期,正如我们所看到的,在底层实际上有很多事情要做。这些大部分都是由框架来处理的,作为一个Spring开发人员,你很少需要经常使用它。然而,当你使用Spring框架进入越来越复杂的应用程序时,有时您必须了解bean生命周期中发生的事情。
我个人不喜欢使用InitializingBean和DisposableBean接口。主要是因为它将代码与Spring紧密地耦合在一起。更好的方法是在bean配置文件中指定init-method和destroy-method属性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容