Spring Bean 的生命周期详解
所谓万物皆对象,对于一个 bean 而言,从出生到死亡,他要经历哪些阶段呢?
生命周期
理解对象的生命周期,可以帮助我们更好的做一些扩展。
一个对象从被创建到被垃圾回收,可以大致分为这 5 个阶段:
- 创建/实例化阶段:调用类的构造方法,产生一个新对象;
- 初始化阶段:此时对象已经被创建了,但还未被正常使用,可以在这里做一些初始化的操作;
- 运行使用期:此时对象已经完全初始化好,程序正常运行,对象被使用;
- 销毁阶段:此时对象准备被销毁,需要预先的把自身占用的资源等处理好(如关闭、释放数据库连接);
- 回收阶段:此时对象已经完全没有被引用了,被垃圾回收器回收。
理解了一个 Bean 的生命周期后,下面我们看下SpringFramework怎么对Bean 的生命周期做干预的。
单实例 Bean 的生命周期
init-method & destroy-method
1)创建 Bean
package com.study.spring.a_initmethod;
public class Cat {
private String name;
public Cat() {
System.out.println("Cat 构造方法执行了。。。");
}
public void setName(String name) {
System.out.println("setName方法执行了。。。");
this.name = name;
}
public void init() {
System.out.println(name + " 被初始化了。。。");
}
public void destroy() {
System.out.println(name + " 被销毁了。。。");
}
}
public class Dog {
private String name;
public Dog() {
System.out.println("Dog 构造方法执行了。。。");
}
public void setName(String name) {
System.out.println("setName方法执行了。。。");
this.name = name;
}
public void init() {
System.out.println(name + "被初始化了。。。");
}
public void destroy() {
System.out.println(name + "被销毁了。。。");
}
}
2)分别创建 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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="com.study.spring.a_initmethod.Cat" init-method="init" destroy-method="destroy">
<property name="name" value="小米米"/>
</bean>
</beans>
@Configuration
public class Config {
@Bean(initMethod = "init",destroyMethod = "destroy")
public Dog dog(){
Dog dog = new Dog();
dog.setName("小勾勾");
return dog;
}
}
3)分别测试xml 和注解驱动
private static void testXml() {
System.out.println("准备初始化IOC容器。。。");
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean-initmethod.xml");
System.out.println("IOC容器初始化完成。。。");
System.out.println();
System.out.println("准备销毁IOC容器。。。");
context.close();
System.out.println("IOC容器销毁完成。。。");
}
private static void testAnnotationConfig() {
System.out.println("准备初始化IOC容器。。。");
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
System.out.println("IOC容器初始化完成。。。");
System.out.println();
System.out.println("准备销毁IOC容器。。。");
applicationContext.close();
System.out.println("IOC容器销毁完成。。。");
}
输出如下:
准备初始化IOC容器。。。
Cat 构造方法执行了。。。
setName方法执行了。。。
小米米 被初始化了。。。
IOC容器初始化完成。。。
准备销毁IOC容器。。。
小米米 被销毁了。。。
IOC容器销毁完成。。。
-----------------------------
准备初始化IOC容器。。。
Dog 构造方法执行了。。。
setName方法执行了。。。
小勾勾被初始化了。。。
IOC容器初始化完成。。。
准备销毁IOC容器。。。
小勾勾被销毁了。。。
IOC容器销毁完成。。。
由此可以得出结论:在 IOC 容器初始化之前,默认情况下 Bean 已经创建好了,而且完成了初始化动作;容器调用销毁动作时,先销毁所有 Bean ,最后 IOC 容器全部销毁完成。
同时也可以看出来,在 Bean 的生命周期中,是先对属性赋值,后执行 init-method
标记的方法。
@PostConstruct & @PreDestroy
上面的 Cat 和 Dog 都是我们手动声明注册的,但是对于那些使用模式注解的 Bean ,这种方式就不好使了,因为没有可以声明 init-method
和 destroy-method
的地方了。
此时可以使用JSR250
规范提供的 @PostConstruct
和 @PreDestroy
这两个注解,分别对应 init-method
和 destroy-method
。
比如,Spring中常用的模式注解:@Component、@Service、@Repository、@Controller,使用这些注解,Spring容器可以在启动时自动扫描并注册这些Bean,而不需要显式地在XML配置文件中声明或在Java配置类中手动注册。
1)创建Bean
@Component
public class Pen {
private Integer ink;
public Pen(){
System.out.println("钢笔的构造方法");
}
@PostConstruct
public void addInk() {
System.out.println("钢笔中已加满墨水。。。");
this.ink = 100;
}
@PreDestroy
public void outWellInk() {
System.out.println("钢笔中的墨水都放干净了。。。");
this.ink = 0;
}
@Override
public String toString() {
return "Pen{" + "ink=" + ink + '}';
}
}
2)测试
public class Client {
public static void main(String[] args) {
System.out.println("准备初始化IOC容器。。。");
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
"com.study.spring.b_jsr250");
System.out.println("IOC容器初始化完成。。。");
System.out.println();
System.out.println("准备销毁IOC容器。。。");
ctx.close();
System.out.println("IOC容器销毁完成。。。");
}
}
输出如下:
准备初始化IOC容器。。。
钢笔的构造方法
钢笔中已加满墨水。。。
IOC容器初始化完成。。。
准备销毁IOC容器。。。
钢笔中的墨水都放干净了。。。
IOC容器销毁完成。。。
可以得出结论:这两个注解实现的效果和 init-method
、 destroy-method
是一样的。
JSR250规范 与 init-method 共存
如果 @PostConstruct
和 @PreDestroy
和 init-method
和 destroy-method
共存,执行顺序是怎样的呢?
1)创建 Bean
public class Pen {
private Integer ink;
public Pen(){
System.out.println("钢笔的构造方法");
}
public void open() {
System.out.println("init method ...打开钢笔");
}
public void close() {
System.out.println("destroy-method - 合上钢笔。。。");
}
@PostConstruct
public void addInk() {
System.out.println("@PostConstruct...钢笔中已加满墨水。。。");
this.ink = 100;
}
@PreDestroy
public void outWellInk() {
System.out.println("@PreDestroy...钢笔中的墨水都放干净了。。。");
this.ink = 0;
}
@Override
public String toString() {
return "Pen{" + "ink=" + ink + '}';
}
}
2)创建配置类
@Configuration
public class JSR250Configuration {
@Bean(initMethod = "open",destroyMethod = "close")
public Pen pen() {
return new Pen();
}
}
3)测试执行
public class Client {
public static void main(String[] args) {
System.out.println("准备初始化IOC容器。。。");
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(JSR250Configuration.class);
System.out.println("IOC容器初始化完成。。。");
System.out.println();
System.out.println("准备销毁IOC容器。。。");
applicationContext.close();
System.out.println("IOC容器销毁完成。。。");
}
}
输出结果:
准备初始化IOC容器。。。
钢笔的构造方法
@PostConstruct...钢笔中已加满墨水。。。
init method ...打开钢笔
IOC容器初始化完成。。。
准备销毁IOC容器。。。
@PreDestroy...钢笔中的墨水都放干净了。。。
destroy-method - 合上钢笔。。。
IOC容器销毁完成。。。
可以得出结论:JSR250 规范的执行优先级高于 init / destroy。
InitializingBean & DisposableBean
是 SpringFramework 内部预先定义好的两个关于生命周期的接口,他们的触发时机与 init-method & destroy-method
、 @PostConstruct
@PreDestroy
一样,都是在 Bean 的初始化和销毁阶段要回调的。
1)创建 Bean
@Component
public class Pen implements InitializingBean, DisposableBean {
private Integer ink;
@Override
public void destroy() throws Exception {
System.out.println("钢笔中的墨水都放干净了。。。");
this.ink = 0;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("钢笔中已加满墨水。。。");
this.ink = 100;
}
@Override
public String toString() {
return "Pen{" + "ink=" + ink + '}';
}
}
2)测试执行
public class InitializingDisposableAnnoApplication {
public static void main(String[] args) throws Exception {
System.out.println("准备初始化IOC容器。。。");
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
"com.study.spring.c_initializingbean");
System.out.println("IOC容器初始化完成。。。");
System.out.println();
System.out.println("准备销毁IOC容器。。。");
ctx.close();
System.out.println("IOC容器销毁完成。。。");
}
}
输出结果:
准备初始化IOC容器。。。
钢笔中已加满墨水。。。
IOC容器初始化完成。。。
准备销毁IOC容器。。。
钢笔中的墨水都放干净了。。。
IOC容器销毁完成。。。
三种生命周期并存
当一个 Bean 同时用这三种生命周期控制时,执行顺序又是怎么样的呢?
1)创建 Bean
@Component
public class Pen implements InitializingBean, DisposableBean {
private Integer ink;
public Pen(){
System.out.println("构造方法");
}
public void open() {
System.out.println("init-method - 打开钢笔。。。");
}
public void close() {
System.out.println("destroy-method - 合上钢笔。。。");
}
@PostConstruct
public void addInk() {
System.out.println("@PostConstruct 钢笔中已加满墨水。。。");
this.ink = 100;
}
@PreDestroy
public void outwellInk() {
System.out.println("@PreDestroy 钢笔中的墨水都放干净了。。。");
this.ink = 0;
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean - 写完字了。。。");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean - 准备写字。。。");
}
@Override
public String toString() {
return "Pen{" + "ink=" + ink + '}';
}
}
2)创建配置类
@Configuration
public class Config {
@Bean(initMethod = "open",destroyMethod = "close")
public Pen pen(){
return new Pen();
}
}
3)测试执行
public class Client {
public static void main(String[] args) {
System.out.println("准备初始化IOC容器。。。");
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
Config.class);
System.out.println("IOC容器初始化完成。。。");
System.out.println();
System.out.println("准备销毁IOC容器。。。");
ctx.close();
System.out.println("IOC容器销毁完成。。。");
}
}
输出结果:
准备初始化IOC容器。。。
构造方法
@PostConstruct 钢笔中已加满墨水。。。
InitializingBean - 准备写字。。。
init-method - 打开钢笔。。。
IOC容器初始化完成。。。
准备销毁IOC容器。。。
@PreDestroy 钢笔中的墨水都放干净了。。。
DisposableBean - 写完字了。。。
destroy-method - 合上钢笔。。。
IOC容器销毁完成。。。
可以看到执行顺序是:@PostConstruct → InitializingBean → init-method
。
原型Bean的生命周期
当面介绍的都是单实例 Bean 的生命周期,而对于原型 Bean,它与单实例 Bean 的生命周期是不一样的。
单实例 Bean 的生命周期是陪着 IOC 容器一起的,容器初始化,单实例 Bean 也跟着初始化(延迟 Bean 例外);
容器销毁,单实例 Bean 也跟着销毁。
原型 Bean 由于每次都是取的时候才产生一个,所以它的生命周期与 IOC 容器无关。
1)创建 Bean
@Component
public class Pen implements InitializingBean, DisposableBean {
private Integer ink;
public Pen(){
System.out.println("构造方法");
}
public void open() {
System.out.println("init-method - 打开钢笔。。。");
}
public void close() {
System.out.println("destroy-method - 合上钢笔。。。");
}
@PostConstruct
public void addInk() {
System.out.println("@PostConstruct 钢笔中已加满墨水。。。");
this.ink = 100;
}
@PreDestroy
public void outWellInk() {
System.out.println("@PreDestroy 钢笔中的墨水都放干净了。。。");
this.ink = 0;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean - 准备写字。。。");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean - 写完字了。。。");
}
@Override
public String toString() {
return "Pen{" + "ink=" + ink + '}';
}
}
2)创建配置类
@Configuration
public class Config {
@Bean(initMethod = "open",destroyMethod = "close")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //原型Bean
public Pen pen(){
return new Pen();
}
}
3)测试执行
public class Client {
public static void main(String[] args) {
System.out.println("准备初始化IOC容器。。。");
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
Config.class);
System.out.println("IOC容器初始化完成。。。");
}
}
输出结果:
准备初始化IOC容器。。。
IOC容器初始化完成。。。
由此得出结论:原型 Bean 的创建不随 IOC 的初始化而创建。
然后,在main 方法中去获取该 Bean,再次执行:
public class Client {
public static void main(String[] args) {
System.out.println("准备初始化IOC容器。。。");
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
Config.class);
System.out.println("IOC容器初始化完成。。。");
System.out.println("准备获取pen");
Pen pen = ctx.getBean(Pen.class);
System.out.println("获取到pen");
}
}
输出结果:
准备初始化IOC容器。。。
IOC容器初始化完成。。。
准备获取pen
构造方法
@PostConstruct 钢笔中已加满墨水。。。
InitializingBean - 准备写字。。。
init-method - 打开钢笔。。。
获取到pen
由此得出结论:原型Bean的初始化动作与单实例Bean完全一致。
接着,我们把销毁 Bean 的代码也加上:
public class Client {
public static void main(String[] args) {
System.out.println("准备初始化IOC容器。。。");
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
Config.class);
System.out.println("IOC容器初始化完成。。。");
System.out.println("准备获取pen");
Pen pen = ctx.getBean(Pen.class);
System.out.println("获取到pen");
System.out.println("用完Pen了,准备销毁。。。");
ctx.getBeanFactory().destroyBean(pen);
System.out.println("Pen销毁完成。。。");
}
}
输出结果:
准备初始化IOC容器。。。
IOC容器初始化完成。。。
准备获取pen
构造方法
@PostConstruct 钢笔中已加满墨水。。。
InitializingBean - 准备写字。。。
init-method - 打开钢笔。。。
获取到pen
用完Pen了,准备销毁。。。
@PreDestroy 钢笔中的墨水都放干净了。。。
DisposableBean - 写完字了。。。
Pen销毁完成。。。
由此得出结论;原型 Bean 在销毁时不处理 destroyMethod
标注的方法。
后置处理器 BeanPostProcessor
BeanPostProcessor
是一个容器的扩展点,它可以在 bean 的生命周期过程中,初始化阶段前后添加自定义处理逻辑,并且不同 IOC 容器间的BeanPostProcessor
不会相互干预。
也可以配置多个BeanPostProcessor
实例,通过设置order
属性来控制BeanPostProcessor
实例的执行顺序。
1)创建 bean
public class Dog implements InitializingBean {
public void initMethod() {
System.out.println("initMethod ...");
}
@PostConstruct
public void postConstruct() {
System.out.println("@PostConstruct ...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean ...");
}
}
2)创建配置类
@Configuration
public class Config {
@Bean(initMethod = "initMethod")
public Dog dog() {
return new Dog();
}
}
3)创建两个后置处理器
@Component
public class InstantiationTracingBeanPostProcessor1 implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Dog) {
System.out.println("【第一个后置处理器】拦截到Bean的初始化之前:" + bean);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Dog) {
System.out.println("【第一个后置处理器】拦截到Bean的初始化之后:" + bean);
}
return bean;
}
@Override
public int getOrder() {
return 1;
}
}
@Component
public class InstantiationTracingBeanPostProcessor2 implements BeanPostProcessor, Ordered {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Dog) {
System.out.println("【第二个后置处理器】拦截到Bean的初始化之前:" + bean);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Dog) {
System.out.println("【第二个后置处理器】拦截到Bean的初始化之后:" + bean);
}
return bean;
}
@Override
public int getOrder() {
return 2;
}
}
4)测试执行
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("com.study.spring.postprocessor");
}
}
输出结果:
【第一个后置处理器】拦截到Bean的初始化之前:com.study.spring.postprocessor.Dog@7f9fcf7f
【第二个后置处理器】拦截到Bean的初始化之前:com.study.spring.postprocessor.Dog@7f9fcf7f
@PostConstruct ...
InitializingBean ...
initMethod ...
【第一个后置处理器】拦截到Bean的初始化之后:com.study.spring.postprocessor.Dog@7f9fcf7f
【第二个后置处理器】拦截到Bean的初始化之后:com.study.spring.postprocessor.Dog@7f9fcf7f
由此得出 bean 的初始化阶段的全流程:BeanPostProcessor#postProcessBeforeInitialization → @PostConstruct → InitializingBean → init-method → BeanPostProcessor#postProcessAfterInitialization
。
参考资料:《从 0 开始深入学习Spring小册》