本文共享自华为云社区《Spring高手之路5——彻底掌握Bean的生命周期》,作者: 砖业洋__ 。

1. 了解Bean的生命周期

1.1 生命周期的各个阶段

Spring IOC容器中,Bean的生命周期大致如下:

  • 实例化:当发动Spring应用时,IOC容器就会为在装备文件中声明的每个<bean>创立一个实例。

  • 特点赋值:实例化后,Spring就经过反射机制给Bean的特点赋值。

  • 调用初始化办法:假如Bean装备了初始化办法,Spring就会调用它。初始化办法是在Bean创立并赋值之后调用,能够在这个办法里边写一些事务处理代码或许做一些初始化的作业。

  • Bean运转期:此时,Bean现已预备好被程序运用了,它现已被初始化并赋值完结。

  • 应用程序封闭:当封闭IOC容器时,Spring会处理装备了毁掉办法的Bean

  • 调用毁掉办法:假如Bean装备了毁掉办法,Spring会在一切Bean都现已运用完毕,且IOC容器封闭之前调用它,能够在毁掉办法里边做一些资源开释的作业,比方封闭衔接、整理缓存等。

这便是Spring IOC容器办理Bean的生命周期,帮助咱们办理对象的创立和毁掉,以及在恰当的机遇做恰当的事情。

咱们能够将生命周期的触发称为回调,因为生命周期的办法是咱们自己界说的,但办法的调用是由结构内部帮咱们完结的,所以能够称之为“回调”。

2. 了解init-method和destroy-method

让咱们先了解一种最容易了解的生命周期阶段:初始化和毁掉办法。这些办法能够在Bean的初始化和毁掉阶段起效果,咱们经过示例来演示这种办法。

为了方便演示XML和注解的办法,接下来咱们会创立两个类来别离进行演示,别离为LionElephant,让咱们一步一步比照观察。

2.1 从XML装备创立Bean看生命周期

先创立一个类Lion

package com.example.demo.bean;
public class Lion {
    private String name;
    public void setName(String name) {
        this.name = name;
    }
    public void init() {
        System.out.println(name + " has been initialized...");
    }
    public void destroy() {
        System.out.println(name + " has been destroyed...");
    }
}

XML中,咱们运用<bean>标签来注册Lion

applicationContext.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.xsd">
        <bean class="com.example.demo.bean.Lion"
              init-method="init" destroy-method="destroy">
            <property name="name" value="simba"/>
        </bean>
</beans>

加上主程序

package com.example.demo.application;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.support.ClassPathXmlApplicationContext;
@ComponentScan("com.example")
public class DemoApplication {
    public static void main(String[] args) {
        System.out.println("Spring容器初始化开端");
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println("Spring容器初始化完结。");
        System.out.println("==================");
        System.out.println("Spring容器预备封闭");
        context.close();
        System.out.println("Spring容器已封闭。");
    }
}

运转成果

带你彻底掌握Bean的生命周期

<bean>标签中,有两个特点:init-methoddestroy-method,这两个特点用于指定初始化和毁掉办法。

这儿”simba has been initialized...“,证明init()办法被调用了。当context.close()被调用时,看到”simba has been destroyed...“,证明destroy()办法被调用了。

IOC 容器初始化之前,默认情况下 Bean 现已创立好了,并且完结了初始化动作;容器调用毁掉动作时,先毁掉一切 Bean ,最后 IOC 容器悉数毁掉完结。

这个比如经过一个简单的Spring应用程序显现了Spring bean的生命周期。咱们能够在创立bean时根据需求运用这些生命周期办法。

2.2 从装备类注解装备创立Bean看生命周期

这儿再创立一个类Elephant和上面比照

package com.example.demo.bean;
public class Elephant {
    private String name;
    public void setName(String name) {
        this.name = name;
    }
    public void init() {
        System.out.println(name + " has been initialized...");
    }
    public void destroy() {
        System.out.println(name + " has been destroyed...");
    }
}

关于注解,@Bean注解中也有类似的特点:initMethoddestroyMethod,这两个特点的效果与XML装备中的相同。

package com.example.demo.configuration;
import com.example.demo.bean.Elephant;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ImportResource("classpath:applicationContext.xml")
public class AnimalConfig {
    @Bean(initMethod = "init", destroyMethod = "destroy")
    public Elephant elephant() {
        Elephant elephant = new Elephant();
        elephant.setName("Dumbo");
        return elephant;
    }
}

这儿用@ImportResource("classpath:applicationContext.xml")引进xml装备创立Bean进行比照。

主程序改为如下:

package com.example.demo.application;
import com.example.demo.configuration.AnimalConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("com.example")
public class DemoApplication {
    public static void main(String[] args) {
        System.out.println("Spring容器初始化开端");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
        System.out.println("Spring容器初始化完结。");
        System.out.println("==================");
        System.out.println("Spring容器预备封闭");
        context.close();
        System.out.println("Spring容器已封闭。");
    }
}

运转成果

带你彻底掌握Bean的生命周期

注意:在Spring中,假如在Java装备中界说了一个Bean,并在XML中界说了一个相同idnameBean,那么最后注册的那个Bean会覆盖之前注册的,这取决于装备文件加载次序,无论在Java装备中仍是XML装备中界说的initMethoddestroyMethod,最后生效的总是后加载的装备中界说的。

init-method”是指定初始化回调办法的特点的总称,无论它是在XML装备仍是Java装备中运用。同样地,“destroy-method”是指定毁掉回调办法的特点的总称。后文咱们讲解多种声明周期共存的时候,将延续这种说法。

2.3 初始化和毁掉办法的特性

Spring结构中装备Bean的初始化和毁掉办法时,需求依照Spring的标准来装备这些办法,不然Spring可能无法正确地调用它们。下面给每个特性提供一个解说和示例:

  • 办法的访问权限无约束:这意味着无论办法是publicprotected仍是privateSpring都能够调用。Spring经过反射来调用这些办法,所以它能够疏忽Java的访问权限约束。示例:

    public class MyBean { private void init() { // 初始化代码 } }

在上述代码中,即便init办法是private的,Spring也能够正常调用。

  • 办法没有参数:因为Spring不知道需求传递什么参数给这些办法,所以这些办法不能有参数。示例:

    public class MyBean { public void init() { // 初始化代码 } }

在上述代码中,init办法没有参数,假如添加了参数,如public void init(String arg)Spring将无法调用此办法。

  • 办法没有回来值:因为回来的值对Spring来说没有意义,所以这些办法不应该有回来值。示例:

    public class MyBean { public void init() { // 初始化代码 } }

在上述代码中,init办法是void的,假如让此办法回来一个值,如public String init(),那么Spring将疏忽此回来值。

  • 办法能够抛出反常:假如在初始化或毁掉进程中产生过错,这些办法能够抛出反常来告诉Spring。示例:

    public class MyBean { public void init() throws Exception { // 初始化代码 if (somethingGoesWrong) { throw new Exception(“Initialization failed.”); } } }

在上述代码中,假如在init办法中的初始化代码犯错,它会抛出一个反常。Spring结构默认会中止Bean的创立,并抛出反常。

2.4 探究Bean的初始化流程次序

在上面的代码中,咱们能够看出BeanIOC容器初始化阶段就现已创立并初始化了,那么每个Bean的初始化动作又是怎么进行的呢?咱们修正一下Lion,在构造办法和setName办法中加入操控台打印,这样在调用这些办法时,会在操控台上得到反馈。

package com.example.demo.bean;
public class Lion {
    private String name;
    public Lion() {
        System.out.println("Lion's constructor is called...");
    }
    public void setName(String name) {
        System.out.println("setName method is called...");
        this.name = name;
    }
    public void init() {
        System.out.println(name + " has been initialized...");
    }
    public void destroy() {
        System.out.println(name + " has been destroyed...");
    }
}

咱们从头运转主程序:

@ComponentScan("com.example")
public class DemoApplication {
    public static void main(String[] args) {
        System.out.println("Spring容器初始化开端");
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println("Spring容器初始化完结。");
        System.out.println("==================");
        System.out.println("Spring容器预备封闭");
        context.close();
        System.out.println("Spring容器已封闭。");
    }
}

运转成果

带你彻底掌握Bean的生命周期

咱们能够得出定论:Bean的生命周期中,首先进行特点赋值,然后履行init-method符号的办法。

3. @PostConstruct和@PreDestroy

JSR250标准中,有两个与Bean生命周期相关的注解,即@PostConstruct@PreDestroy。这两个注解对应了Bean的初始化和毁掉阶段。

@PostConstruct注解符号的办法会在bean特点设置完毕后(即完结依靠注入),但在bean对外露出(即能够被其他bean引用)之前被调用,这个机遇一般用于完结一些初始化作业。

@PreDestroy注解符号的办法会在Spring容器毁掉bean之前调用,这一般用于开释资源。

3.1 示例:@PostConstruct和@PreDestroy的运用

咱们这儿仍是用Lion类来创立这个比如,将Lion类修正为运用@PostConstruct@PreDestroy注解

package com.example.demo.bean;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class Lion {
    private String name;
    public void setName(String name) {
        this.name = name;
    }
    @PostConstruct
    public void init() {
        System.out.println("Lion is going through init.");
    }
    @PreDestroy
    public void destroy() {
        System.out.println("Lion is going through destroy.");
    }
    @Override
    public String toString() {
        return "Lion{" + "name=" + name + '}';
    }
}

Lion类加上@Component注解,让IOC容器去办理这个类,咱们这儿就不把Elephant类加进来增加了解难度了。

@PostConstruct@PreDestroy 注解标注的办法与 init-method / destroy-method 办法的初始化和毁掉的要求是一样的,访问修饰符没有约束,private也能够。

咱们能够注释掉之前的装备类和XML装备,因为和这儿的比如没有关系,咱们来看看主程序:

package com.example.demo.application;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
    public static void main(String[] args) {
        System.out.println("Spring容器初始化开端");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.example.demo.bean");
        System.out.println("Spring容器初始化完结。");
        System.out.println("==================");
        System.out.println("Spring容器预备封闭");
        context.close();
        System.out.println("Spring容器已封闭。");
    }
}

运转成果

带你彻底掌握Bean的生命周期

这儿能够看到@PostConstruct@PreDestroy注解正确地应用在了Lion的初始化和毁掉进程中。

3.2 初始化和毁掉——注解和init-method共存比照

@PostConstruct@PreDestroy注解与init-method/destroy-method特点怎么共存呢?咱们来看看

咱们只用Lion类来举比如,在Lion类中添加新的open()close()办法

需求的悉数代码如下:

Lion.java

package com.example.demo.bean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class Lion {
    private String name;
    public Lion() {
        System.out.println("Lion构造器");
    }
    public void setName(String name) {
        System.out.println("Lion设置name");
        this.name = name;
    }
    public void open() {
        System.out.println("装备类initMethod - 打开Lion。。。");
    }
    public void close() {
        System.out.println("装备类destroyMethod - 封闭Lion。。。");
    }
    @PostConstruct
    public void init() {
        System.out.println("@PostConstruct - Lion正在进行初始化。。。");
    }
    @PreDestroy
    public void destroy() {
        System.out.println("@PreDestroy - Lion正在进行毁掉。。。");
    }
    @Override
    public String toString() {
        return "Lion{" + "name=" + name + '}';
    }
}

装备类AnimalConfig.java

package com.example.demo.configuration;
import com.example.demo.bean.Lion;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AnimalConfig {
    @Bean(initMethod = "open", destroyMethod = "close")
    public Lion lion() {
        return new Lion();
    }
}

主程序

package com.example.demo.application;
import com.example.demo.configuration.AnimalConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
    public static void main(String[] args) {
        System.out.println("Spring容器初始化开端");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
        System.out.println("Spring容器初始化完结。");
        System.out.println("==================");
        System.out.println("Spring容器预备封闭");
        context.close();
        System.out.println("Spring容器已封闭。");
    }
}

运转成果

带你彻底掌握Bean的生命周期

这儿能够看到@PostConstruct@PreDestroy注解的优先级始终高于装备类中@Bean注解的initMethoddestroyMethod特点。

4. 完结InitializingBean和DisposableBean接口

这两个接口是 Spring 预界说的两个关于生命周期的接口。他们被触发的机遇与上文中的 init-method / destroy-method 以及 JSR250 标准的注解相同,都是在 Bean 的初始化和毁掉阶段回调的。下面演示怎么运用这两个接口。

4.1 示例:完结InitializingBean和DisposableBean接口

创立Bean,咱们让Lion类完结这两个接口:

Lion.java

package com.example.demo.bean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
@Component
public class Lion implements InitializingBean, DisposableBean {
    private Integer energy;
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("狮子现已充溢能量。。。");
        this.energy = 100;
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("狮子现已耗费完一切能量。。。");
        this.energy = 0;
    }
    @Override
    public String toString() {
        return "Lion{" + "energy=" + energy + '}';
    }
}

InitializingBean接口只要一个办法:afterPropertiesSet()。在Spring结构中,当一个bean的一切特点都现已被设置完毕后,这个办法就会被调用。也便是说,这个bean一旦被初始化,Spring就会调用这个办法。咱们能够在bean的一切特点被设置后,进行一些自界说的初始化作业。

DisposableBean接口也只要一个办法:destroy()。当Spring容器封闭并毁掉bean时,这个办法就会被调用。咱们能够在bean被毁掉前,进行一些整理作业。

主程序:

package com.example.demo.application;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
    public static void main(String[] args) {
        System.out.println("Spring容器初始化开端");
        AnnotationConfigApplicationContext context
                = new AnnotationConfigApplicationContext("com.example.demo.bean");
        System.out.println("Spring容器初始化完结。");
        System.out.println("==================");
        System.out.println("Spring容器预备封闭");
        context.close();
        System.out.println("Spring容器已封闭。");
    }
}

运转成果:

带你彻底掌握Bean的生命周期

4.2 三种生命周期并存

Spring结构中,操控Bean生命周期的三种办法是:

  1. 运用Springinit-methoddestroy-method(在XML装备或许Java装备中自界说的初始化和毁掉办法);
  2. 运用JSR-250标准的@PostConstruct@PreDestroy注解;
  3. 完结SpringInitializingBeanDisposableBean接口。

  接下来咱们测验一下,一个Bean一同界说init-methoddestroy-method办法,运用@PostConstruct@PreDestroy注解,以及完结InitializingBeanDisposableBean接口,履行次序是怎样的。

咱们创立一个新的类Lion2,并一同进行三种办法的生命周期操控:

需求运转的悉数代码如下:

package com.example.demo.bean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class Lion2 implements InitializingBean, DisposableBean {
    private Integer energy;
    public void open() {
        System.out.println("init-method - 狮子开端举动。。。");
    }
    public void close() {
        System.out.println("destroy-method - 狮子完毕举动。。。");
    }
    @PostConstruct
    public void gainEnergy() {
        System.out.println("@PostConstruct - 狮子现已充溢能量。。。");
        this.energy = 100;
    }
    @PreDestroy
    public void loseEnergy() {
        System.out.println("@PreDestroy - 狮子现已耗费完一切能量。。。");
        this.energy = 0;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean - 狮子预备举动。。。");
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean - 狮子举动完毕。。。");
    }
}

接着,咱们注册Lion2

package com.example.demo.configuration;
import com.example.demo.bean.Lion2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AnimalConfig {
    @Bean(initMethod = "open", destroyMethod = "close")
    public Lion2 lion2() {
        return new Lion2();
    }
}

然后让注解 IOC 容器驱动这个装备类,主程序如下:

package com.example.demo.application;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
    public static void main(String[] args) {
        System.out.println("Spring容器初始化开端");
        AnnotationConfigApplicationContext context
                = new AnnotationConfigApplicationContext("com.example.demo");
        System.out.println("Spring容器初始化完结。");
        System.out.println("==================");
        System.out.println("Spring容器预备封闭");
        context.close();
        System.out.println("Spring容器已封闭。");
    }
}

运转成果:

带你彻底掌握Bean的生命周期

从上面的成果,咱们能够得出以下定论,在Spring结构中单实例Bean的初始化和毁掉进程有这样的履行次序:

初始化次序:@PostConstruct → InitializingBean → init-method
毁掉次序:@PreDestroy → DisposableBean → destroy-method

在初始化Bean时,@PostConstruct注解办法会首先被履行,然后是完结InitializingBean接口的afterPropertiesSet办法,最后是init-method指定的办法。

在毁掉Bean时,@PreDestroy注解办法会首先被履行,然后是完结DisposableBean接口的destroy办法,最后是destroy-method指定的办法

结合前面说过的特点赋值(构造器办法和setter办法),简单总结一下Spring Bean生命周期的流程:

  1. 实例化(经过构造器办法);
  2. 设置Bean的特点(经过setter办法);
  3. 调用Bean的初始化办法(@PostConstructafterPropertiesSet办法或许init-method指定的办法);
  4. Bean能够被应用程序运用;
  5. 当容器封闭时,调用Bean的毁掉办法(@PreDestroydestroy办法或许destroy-method指定的办法)。

5. 原型Bean的生命周期

原型Bean的创立和初始化进程与单例Bean类似,但因为原型Bean的性质,其生命周期与IOC容器的生命周期并不相同。

这儿展示一下需求的悉数代码。

Lion2.java

package com.example.demo.bean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class Lion2 implements InitializingBean, DisposableBean {
    private Integer energy;
    public void roar() {
        System.out.println("The lion is roaring...");
    }
    public void rest() {
        System.out.println("The lion is resting...");
    }
    @PostConstruct
    public void gainEnergy() {
        System.out.println("@PostConstruct - 狮子现已充溢能量。。。");
        this.energy = 100;
    }
    @PreDestroy
    public void loseEnergy() {
        System.out.println("@PreDestroy - 狮子现已耗费完一切能量。。。");
        this.energy = 0;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean - 狮子预备举动。。。");
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean - 狮子举动完毕。。。");
    }
}

然后在SpringJava装备中声明并设定其为原型Bean

package com.example.demo.configuration;
import com.example.demo.bean.Lion2;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Configuration
public class PrototypeLifecycleConfiguration {
    @Bean(initMethod = "roar", destroyMethod = "rest")
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Lion2 lion() {
        return new Lion2();
    }
}

假如咱们只是发动了IOC容器,但并未恳求Lion2的实例,Lion Bean的初始化不会马上产生。也便是说,原型Bean不会随着IOC容器的发动而初始化。以下是发动容器但并未恳求Bean的代码:

package com.example.demo.application;
import com.example.demo.configuration.PrototypeLifecycleConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
    public static void main(String[] args) {
        System.out.println("Spring容器初始化开端");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
                PrototypeLifecycleConfiguration.class);
    }
}

运转成果:

带你彻底掌握Bean的生命周期

当咱们明确恳求一个Lion2的实例时,咱们会看到一切的初始化办法依照预订的次序履行,这个次序跟单例Bean完全一致:

package com.example.demo.application;
import com.example.demo.bean.Lion2;
import com.example.demo.configuration.PrototypeLifecycleConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class DemoApplication {
    public static void main(String[] args) {
        System.out.println("Spring容器初始化开端");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
                PrototypeLifecycleConfiguration.class);
        System.out.println("Ready to get a Lion instance...");
        Lion2 lion = context.getBean(Lion2.class);
        System.out.println("A Lion instance has been fetched...");
        System.out.println("Lion instance is no longer needed, preparing to destroy...");
        context.getBeanFactory().destroyBean(lion);
        System.out.println("Lion instance has been destroyed...");
    }
}

运转成果:

带你彻底掌握Bean的生命周期

将原型Bean和单例Bean的三种生命周期进行比照后发现,调用IOC容器的destroyBean()办法毁掉原型Bean时,只要@PreDestroy注解和DisposableBean接口的destroy办法会被触发,而被destroy-method符号的自界说毁掉办法并不会被履行。

从这儿咱们能够得出定论:在毁掉原型Bean时,Spring不会履行由destroy-method符号的自界说毁掉办法,所以原型Beandestroy-method的也有局限性。假如有重要的整理逻辑需求在Bean毁掉时履行,那么应该将这部分逻辑放在@PreDestroy注解的办法或DisposableBean接口的destroy办法中。

6. Spring中操控Bean生命周期的三种办法总结

带你彻底掌握Bean的生命周期

欢迎一键三连~

有问题请留言,我们一同探讨学习

点击重视,第一时间了解华为云新鲜技能~