当前位置: 首页 > article >正文

Spring Bean 的生命周期详解

所谓万物皆对象,对于一个 bean 而言,从出生到死亡,他要经历哪些阶段呢?

生命周期

理解对象的生命周期,可以帮助我们更好的做一些扩展。

在这里插入图片描述

一个对象从被创建到被垃圾回收,可以大致分为这 5 个阶段:

  1. 创建/实例化阶段:调用类的构造方法,产生一个新对象;
  2. 初始化阶段:此时对象已经被创建了,但还未被正常使用,可以在这里做一些初始化的操作;
  3. 运行使用期:此时对象已经完全初始化好,程序正常运行,对象被使用;
  4. 销毁阶段:此时对象准备被销毁,需要预先的把自身占用的资源等处理好(如关闭、释放数据库连接);
  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-methoddestroy-method 的地方了。

此时可以使用JSR250 规范提供的 @PostConstruct@PreDestroy 这两个注解,分别对应 init-methoddestroy-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-methoddestroy-method 是一样的。

JSR250规范 与 init-method 共存

如果 @PostConstruct@PreDestroyinit-methoddestroy-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小册》


http://www.kler.cn/a/404557.html

相关文章:

  • 【Nginx从入门到精通】05-安装部署-虚拟机不能上网简单排错
  • 虚拟机上搭建达梦DSC简略步骤
  • qt学习:linux监听键盘alt+b和鼠标移动事件
  • 嵌入式系统中QT实现网络通信方法
  • (十八)JavaWeb后端开发案例——会话/yml/过滤器/拦截器
  • 同三维T80004EHU 高清HDMI/USB编码器
  • 笔记--(Shell脚本01)、正则表达式与文本处理器
  • 零基础Java第二十二期:异常(二)
  • 【Nginx从入门到精通】05-安装部署-虚拟机不能上网简单排错
  • 大语言模型与图结构的融合: 推荐系统中的新兴范式
  • 基于LLama_factory的Qwen2.5大模型的微调笔记
  • Scala中的集合复习(1)
  • 2024中国报业技术年会 | 文盾信息聚焦AI大模型的内容安全风控实践
  • Easyexcel(3-文件导出)
  • 鸿蒙多线程开发——线程间数据通信对象02
  • 用Python爬虫“偷窥”1688商品详情:一场数据的奇妙冒险
  • Scala的Array多维数组
  • 介绍一下strncmp(c基础)
  • 大学课程项目中的记忆深刻 Bug —— 一次意外的数组越界
  • 【Linux】系统调用和库函数汇总整理
  • Ubuntu安装sublime Tex
  • FreeRTOS消息队列实验与出现的问题
  • uni-app 修改复选框checkbox选中后背景和字体颜色
  • redis实现计数器功能
  • 如何取消分词搜索
  • GPT1.0 和 GPT2.0 的联系与区别