本文共享自华为云社区《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
和注解的办法,接下来咱们会创立两个类来别离进行演示,别离为Lion
和Elephant
,让咱们一步一步比照观察。
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>
标签中,有两个特点:init-method
和destroy-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
注解中也有类似的特点:initMethod
和destroyMethod
,这两个特点的效果与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容器已封闭。");
}
}
运转成果
注意:在Spring
中,假如在Java
装备中界说了一个Bean
,并在XML
中界说了一个相同id
或name
的Bean
,那么最后注册的那个Bean
会覆盖之前注册的,这取决于装备文件加载次序,无论在Java
装备中仍是XML
装备中界说的initMethod
或destroyMethod
,最后生效的总是后加载的装备中界说的。
“init-method
”是指定初始化回调办法的特点的总称,无论它是在XML
装备仍是Java
装备中运用。同样地,“destroy-method
”是指定毁掉回调办法的特点的总称。后文咱们讲解多种声明周期共存的时候,将延续这种说法。
2.3 初始化和毁掉办法的特性
在Spring
结构中装备Bean
的初始化和毁掉办法时,需求依照Spring
的标准来装备这些办法,不然Spring
可能无法正确地调用它们。下面给每个特性提供一个解说和示例:
-
办法的访问权限无约束:这意味着无论办法是
public
、protected
仍是private
,Spring
都能够调用。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的初始化流程次序
在上面的代码中,咱们能够看出Bean
在IOC
容器初始化阶段就现已创立并初始化了,那么每个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
的生命周期中,首先进行特点赋值,然后履行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容器已封闭。");
}
}
运转成果
这儿能够看到@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容器已封闭。");
}
}
运转成果
这儿能够看到@PostConstruct
和@PreDestroy
注解的优先级始终高于装备类中@Bean
注解的initMethod
和destroyMethod
特点。
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容器已封闭。");
}
}
运转成果:
4.2 三种生命周期并存
在Spring
结构中,操控Bean
生命周期的三种办法是:
- 运用
Spring
的init-method
和destroy-method
(在XML
装备或许Java
装备中自界说的初始化和毁掉办法); - 运用
JSR-250
标准的@PostConstruct
和@PreDestroy
注解; - 完结
Spring
的InitializingBean
和DisposableBean
接口。
接下来咱们测验一下,一个Bean
一同界说init-method
、destroy-method
办法,运用@PostConstruct
、@PreDestroy
注解,以及完结InitializingBean
、DisposableBean
接口,履行次序是怎样的。
咱们创立一个新的类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容器已封闭。");
}
}
运转成果:
从上面的成果,咱们能够得出以下定论,在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
生命周期的流程:
- 实例化(经过构造器办法);
- 设置
Bean
的特点(经过setter
办法); - 调用
Bean
的初始化办法(@PostConstruct
、afterPropertiesSet
办法或许init-method
指定的办法); -
Bean
能够被应用程序运用; - 当容器封闭时,调用
Bean
的毁掉办法(@PreDestroy
、destroy
办法或许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 - 狮子举动完毕。。。");
}
}
然后在Spring
的Java
装备中声明并设定其为原型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);
}
}
运转成果:
当咱们明确恳求一个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
的三种生命周期进行比照后发现,调用IOC
容器的destroyBean()
办法毁掉原型Bean
时,只要@PreDestroy
注解和DisposableBean
接口的destroy
办法会被触发,而被destroy-method
符号的自界说毁掉办法并不会被履行。
从这儿咱们能够得出定论:在毁掉原型Bean
时,Spring
不会履行由destroy-method
符号的自界说毁掉办法,所以原型Bean
的destroy-method
的也有局限性。假如有重要的整理逻辑需求在Bean
毁掉时履行,那么应该将这部分逻辑放在@PreDestroy
注解的办法或DisposableBean
接口的destroy
办法中。
6. Spring中操控Bean生命周期的三种办法总结
欢迎一键三连~
有问题请留言,我们一同探讨学习
点击重视,第一时间了解华为云新鲜技能~