Spring系列13:bean的生命周期

本文内容

  1. bean的完整的生命周期
  2. 生命周期回调接口
  3. Aware接口详解

Spring Bean的生命周期

面试热题:请描述下Spring的生命周期?

4大生命周期

从源码角度来说,简单分为4大阶段: 实例化 -> 属性赋值 -> 初始化 -> 销毁

  1. 实例化 Instantiation
  2. 属性赋值 Populate
  3. 初始化 Initialization
  4. 销毁 Destruction

实例化和属性赋值对应构造方法和 setter 方法的注入,初始化和销毁是用户能自定义扩展的两个阶段。在这四步之间穿插了各种Spring提供的容器扩展点。

看下源码实现 AbstractAutowireCapableBeanFactory#doCreateBean , 无关源码已经省略,会保留一定的源码的英文注释

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
            throws BeanCreationException {

        // Instantiate the bean.
        BeanWrapper instanceWrapper = null;
        if (instanceWrapper == null) {
            // 1 实例化阶段
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }

        // Initialize the bean instance.
        Object exposedObject = bean;
        try {
            // 2 属性赋值阶段
            populateBean(beanName, mbd, instanceWrapper);
            // 3 初始化阶段
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }

        return exposedObject;
    }

bean销毁阶段源码可以看下 ConfigurableApplicationContext#close(),最终每个bean会调到 DisposableBeanAdapter#destroy() 方法,比较简单。

class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
@Override
    public void destroy() {
        if (this.invokeDisposableBean) {
            try {
                //  1 实现DisposableBean 销毁
                else {
                    ((DisposableBean) this.bean).destroy();
                }
            }
        }

        if (this.destroyMethod != null) {
            // 2 自定义销毁方法
            invokeCustomDestroyMethod(this.destroyMethod);
        }
        else if (this.destroyMethodName != null) {
            Method methodToInvoke = determineDestroyMethod(this.destroyMethodName);
            if (methodToInvoke != null) {
                invokeCustomDestroyMethod(ClassUtils.getInterfaceMethodIfPossible(methodToInvoke));
            }
        }
    }
}

生命周期扩展点

Spring 之所以强大的原因是易扩展,生命周期相关的常用扩展点非常多。扩展点分2类:

  • 作用于多个bean的增强扩展
  • InstantiationAwareBeanPostProcessor 作用于实例化阶段前后
  • BeanPostProcessor 作用于初始化阶段前后
  • InstantiationAwareBeanPostProcessor 作用于销毁阶段前
  • 作用于单个bean的增强扩展
  • 初始化阶段 3个 Aware 接口: BeanNameAware BeanClassLoaderAware BeanFactoryAware InitializingBean 接口 自定义的初始化方法
  • 销毁阶段 DisposableBean 接口 自定义的销毁方法

来一张汇总图,直观明了。

Spring系列13:bean的生命周期

提示:
BeanNameAware BeanClassLoaderAware BeanFactoryAware是在初始化阶段调用对应的接口方法设置的;而其它Aware接口如 EnvironmentAware、EmbeddedValueResolverAware、ApplicationContextAware(ResourceLoaderAware\ApplicationEventPublisherAware\MessageSourceAware)是在初始化前通过 BeanPostProcessor#postProcessBeforeInitialization() 来调用对应接口设置的。
后面有机会写Spring源码的时候再深入。

bean生命周期回调

挖个坟纠个错,在Spring系列2:Spring容器基本概念和使用 中我们提到:

非常建议阅读 BeanFactory 的源码上的注释说明,非常的详尽,常见的面试题:请描述下Spring的生命周期?注释上就有非常官方的完整说明

其实此处表述有误,准确来说如下的源码注释写的是完整的生命周期回调,局限于bean的初始化阶段和销毁阶段。完整bean的生命周期看上一小节的分析。

Spring系列13:bean的生命周期

初始化化阶段完整的调用过程整理如下:

Spring系列13:bean的生命周期

容器对 bean 生命周期的管理提供了生命周期接口,允许开发者对bean的初始化和销毁等生命周期中进行自定义的操作。

bean 初始化回调3种

Spring提供了3种方式进行bean的初始化回调:

  1. InitializingBean 接口 org.springframework.beans.factory.InitializingBean 接口让 bean 在容器设置了 bean 的所有必要属性后执行初始化工作。这种方式有个弊端是类中耦合了Spirng容器。
  2. xml中 <bean></bean>指定 init-method方法

  1. 使用@PostConstruct注解

既然提供了3种,那么不禁会有疑问:

  • 同时使用3种方式,指定3个不同的方法,执行顺序是如何的?
  • 同时使用3种方式,指定的是同一个方法,执行次数是多少次,3次?

直接通过案例来验证。

案例1:3种方式3个不同方法

类的定义

public class BeanOne implements InitializingBean {
    // 1 实现接口的方式
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("BeanOne InitializingBean afterPropertiesSet");
    }

    // 通过xml init-method 配置的方式
    public void myInit() {
        System.out.println("BeanOne init-method myInit");
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println("BeanOne PostConstruct postConstruct");
    }
}

通过xml配置文件的方式定义bean信息


运行测试

    @org.junit.Test
    public void test1() {
        System.out.println("开始初始化容器");
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo11/spring.xml");
        System.out.println("容器使用中----");
        BeanOne beanOne = context.getBean(BeanOne.class);
        System.out.println(beanOne);
        System.out.println("开始销毁容器");
        context.close();
        System.out.println("结束销毁容器");
    }

测试结果

开始初始化容器
BeanOne PostConstruct postConstruct
BeanOne InitializingBean afterPropertiesSet
BeanOne init-method myInit
容器使用中----
com.crab.spring.ioc.demo11.BeanOne@f0f2775
开始销毁容器
结束销毁容器

结论:@PostConstruct > InitializingBean > xml init-method

案例2:3种方式指定同一个方法

类定义如下

public class BeanTwo implements InitializingBean {
    // 1 实现接口的方式
    // 2 通过xml init-method 配置的方式
    // 3 注解方式
    @PostConstruct
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("BeanTwo InitializingBean afterPropertiesSet");
    }
}

xml配置文件和测试程序和上面的类似,不重复。

运行结果如下

开始初始化容器
BeanTwo InitializingBean afterPropertiesSet
容器使用中----
com.crab.spring.ioc.demo11.BeanTwo@3f200884
开始销毁容器
结束销毁容器

结论:3种方式指定同一方法,只会回调一次,不会重复调用

思考下: 一个类中配置2个@PostConstruct注解的初始化方法 init1()和 init2() ,回调初始化哪一个?

bean的销毁回调

类似初始化回调,Spring提供了3种方式进行bean的销毁回调:

  1. 实现 DisposableBean接口
  2. xml中配置destroy-method
  3. 使用@PreDestroy

类似执行顺序和次数结论:

  • 3种方式指定3个不同方法,回调顺序:@PreDestroy > DisposableBean > xml中配置destroy-method
  • *3种方式指定同一个方法,只回调1次

综合案例

定义类

public class BeanThree implements DisposableBean {

    // 方式1 实现DisposableBean
    @Override
    public void destroy() throws Exception {
        System.out.println("BeanThree DisposableBean destroy");
    }
    // 方式2 xml中配置destroy-method
    public void destroy2(){
        System.out.println("BeanThree destroy-method destroy3");
    }
    // 方式3 使用 @PreDestroy 注解
    @PreDestroy
    public void destroy3(){
        System.out.println("BeanThree @PreDestroy destroy3");
    }
}

xml中配置销毁回调


测试程序和结果

    @org.junit.Test
    public void test3() {
        System.out.println("开始初始化容器");
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo11/spring3.xml");
        System.out.println("容器使用中----");
        BeanThree beanOne = context.getBean(BeanThree.class);
        System.out.println(beanOne);
        System.out.println("开始销毁容器");
        context.close();
        System.out.println("结束销毁容器");
    }

// 结果对照结论看
开始初始化容器
容器使用中----
com.crab.spring.ioc.demo11.BeanThree@f0f2775
开始销毁容器
BeanThree @PreDestroy destroy3
BeanThree DisposableBean destroy
BeanThree destroy-method destroy3
结束销毁容器

思考下:xml配置中如何配置全局默认的初始化和销毁回调方法,而不用每个bean都配置?default-init-method default-destroy-method

Aware 接口详解

原理解析

Aware 是一个标记超接口,Spring 提供了广泛的 Aware 回调接口实现,让 bean 向容器获取它们需要特定的基础设施依赖项。

public interface Aware {}

来看一下 接口

public interface ApplicationContextAware {
    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

当 ApplicationContext 创建一个实现 org.springframework.context.ApplicationContextAware 接口的对象实例时,会为该实例提供对该 ApplicationContext 的引用。直接上案例。

定义一个类实现 ApplicationContextAware

public class BeanFour implements ApplicationContextAware {
    // 用于获取初始该类对象的容器对象ApplicationContext
    private ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }
    public ApplicationContext getContext() {
        return context;
    }
}

@Configuration
@ComponentScan
public class AppConfig {
}

测试程序和结果

@org.junit.Test
public void test_aware() {
    AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext(AppConfig.class);
    BeanFour bean = context.getBean(BeanFour.class);
    System.out.println(bean.getContext() == context);
    context.close();
}
// 结果
true

从结果看,BeanFour实例已获取到创建它的容器对象。

使用 Aware 接口主要目的是获取容器中相关的基础对象,也就是依赖注入,但这样做的弊端是将应用程序类和Spring强耦合在一起了。换个角度,依赖注入通过 @Autowired 也可以实现,耦合更低。

@Component
public class BeanFour2  {
    // 用于获取初始该类对象的容器对象ApplicationContext
    @Autowired
    private ApplicationContext context;

    public ApplicationContext getContext() {
        return context;
    }
}

Aware 接口汇总

Spring 提供了广泛的 Aware 回调接口,让 bean 向容器指示它们需要特定的基础设施依赖项,如下表。作为一般规则,名称表示依赖类型。

接口名 ApplicationContextAware ApplicationEventPublisherAware BeanClassLoaderAware BeanFactoryAware BeanNameAware LoadTimeWeaverAware MessageSourceAware NotificationPublisherAware ResourceLoaderAware

总结

本文介绍各种bean的完整的生命周期、生命周期回调接口和 Aware接口。

本篇源码地址: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo11

知识分享,转载请注明出处。学无先后,达者为先!

Original: https://www.cnblogs.com/kongbubihai/p/15878259.html
Author: kongxubihai
Title: Spring系列13:bean的生命周期

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/572056/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • OutOfMemoryError异常

    除了程序计数器外,虚拟机内存在其他几个运行时区域都有发生OutOfMemoryError异常的可能。 Java堆溢出 设置Idea堆的大小为20MB,不可扩展(-Xms参数与最大值…

    Java 2023年6月9日
    077
  • 多图流带你玩转CODING DevOps

    首先介绍一下什么是CODING DevOps,这里套用官网介绍 依托业界领先的敏捷项目管理理念与 DevOps 体系方法论,我们将这些优秀的理念与工具融入至产品中,打通了研发过程中…

    Java 2023年6月14日
    091
  • JDK成长记4:ArrayList常用方法源码探索(下)

    写在前面的话 写在前面的话 有的同学问我,开始讲的很基础,节奏比较慢,这个是因为一个为了让大家慢慢进入状态,后面的节奏会越来越快的,大家不要着急,另一个是因为简单的东西重复,温故而…

    Java 2023年6月5日
    0101
  • Java基础之 逻辑运算符、位运算符

    逻辑运算符 1 public class Demo05 { 2 public static void main(String[] args) { 3 // 与(and) 或(or)…

    Java 2023年6月8日
    093
  • 程序设计基础·Java学习笔记·面向对象(下)

    Java程序设计基础之面向对象(下) (补充了上的一些遗漏的知识,同时加入了自己的笔记的ヾ(•ω•`)o) (至于为什么分P,啊大概是为了自己查笔记方便(?)应该是(〃` 3′〃)…

    Java 2023年6月7日
    084
  • 【数据结构】了解KMP算法和部分匹配值、以及next函数值

    最近生活发生了很多变化,没变的是自己还是咸鱼一条,害~~ 1、什么是 KMP 算法 KMP算法是一种改进的字符串匹配算法。 2、KMP算法的思想 KMP算法的关键是利用匹配失败后的…

    Java 2023年6月5日
    088
  • Linux中CentOS 7版本安装JDK、Tomcat、MySQL、lezsz、maven软件详解

    软件安装 在Linux系统中,安装软件的方式主要有四种,这四种安装方式的特点如下: 安装方式 特点 二进制发布包安装 软件已经针对具体平台编译打包发布,只要解压,修改配置即可 rp…

    Java 2023年6月15日
    066
  • jvm:内存结构与对象内存解析

    java的跨平台性主要是因为其字节码文件可以在任何具有java虚拟机的计算机或者电子设备上运行,jvm中的字节码解析器负责将字节码文件解释成机器码运行,字节码文件.class是ja…

    Java 2023年6月7日
    076
  • Spring与Web环境集成

    Spring与Web环境集成 1. ApplicationContext应用上下文获取方式 应用上下文对象是通过 new ClassPathXmlApplicationContex…

    Java 2023年6月5日
    095
  • Android学习笔记——Android签名机制详解

    Android签名机制详解 近期由于工作需要在学习 Android 的签名机制,因为没有现成资料,只能通过开发者文档和阅读博客的方式对 Android 签名机制进行大致了解。过程中…

    Java 2023年6月8日
    065
  • Nginx: 解决反代时,超过1分钟Gateway Timeout 504问题

    打开Nginx的配置文件中,在对应的反代域名下,添加 proxy_connect_timeout 300;proxy_send_timeout 300;proxy_read_tim…

    Java 2023年5月30日
    075
  • 会话技术 cookie 和 Session(1)

    CookieCookie 属于客户端会话技术,它是服务器发送给浏览器的小段文本信息,存储在客户端浏览器的内存中或硬盘上。当浏览器保存了Cookie 后,每次访问服务器,都会在HTT…

    Java 2023年6月9日
    089
  • Java基础中Int类型变量值互换的几种方法

    在很多时候,我们会使用到将两个 整型变量值进行互换,比如冒泡排序,通过判断来将数组变量的值逐步交换,那么怎么交换值才能最有效最节省空间呢? 首先,我们会想到的,用一个零时变量来做中…

    Java 2023年6月5日
    046
  • 拒绝蛮力,高效查看Linux日志文件!

    原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。 简介 日常分析问题时,会频繁地查看分析日志,但如果蛮力去查看日志,耗时费力还不一定有效果,因此我总结…

    Java 2023年6月7日
    097
  • 【Java面试手册-算法篇】给定一个数字,请判断是否为回文数字?

    在回答这个问题之前,首先得清楚什么是回文数字,回文数字有什么特征。 回文数字:设n是一任意自然数,若将n的各位数字反向排列所得自然数n1与n相等,则称n为一回文数。通俗地说,回文数…

    Java 2023年6月8日
    060
  • springcloud

    转载于狂神老师 ,本文仅作为笔记使用 回顾之前的知识~●JavaSE ●数据库●前端●Servlet ●Http ●Mybatis ●Spring ●SpringMVC ●Spri…

    Java 2023年6月9日
    051
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球