【Spring注解驱动开发】Spring中的InitializingBean和DisposableBean,你真的了解吗?

简介: 在《【Spring注解驱动开发】如何使用@Bean注解指定初始化和销毁的方法?看这一篇就够了!!》一文中,我们讲述了如何使用@Bean注解来指定bean初始化和销毁的方法。具体的用法就是在@Bean注解中使用init-method属性和destroy-method属性来指定初始化方法和销毁方法。除此之外,Spring中是否还提供了其他的方式来对bean实例进行初始化和销毁呢?

InitializingBean接口

1.InitializingBean接口概述

Spring中提供了一个InitializingBean接口,InitializingBean接口为bean提供了属性初始化后的处理方法,它只包括afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。InitializingBean接口的源码如下所示。

package org.springframework.beans.factory;
public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

根据InitializingBean接口中提供的afterPropertiesSet()方法的名字可以推断出:afterPropertiesSet()方法是在属性赋好值之后调用的。那到底是不是这样呢?我们来分析下afterPropertiesSet()方法的调用时机。

2.何时调用InitializingBean接口?

我们定位到Spring中的org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory类下的invokeInitMethods()方法中,来查看Spring加载bean的方法。

题外话:不要问我为什么会是这个invokeInitMethods()方法,如果你和我一样对Spring的源码非常熟悉的话,你也会知道是这个invokeInitMethods()方法,哈哈哈哈!所以,小伙伴们不要只顾着使用Spring,还是要多看看Spring的源码啊!Spring框架中使用了大量优秀的设计模型,其代码的编写规范和严谨程度也是业界开源框架中数一数二的,非常值得阅读。

我们来到AbstractAutowireCapableBeanFactory类下的invokeInitMethods()方法,如下所示。

protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
    throws Throwable {
    //判断该bean是否实现了实现了InitializingBean接口,如果实现了InitializingBean接口,则调用bean的afterPropertiesSet方法
    boolean isInitializingBean = (bean instanceof InitializingBean);
    if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
        if (logger.isTraceEnabled()) {
            logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
        }
        if (System.getSecurityManager() != null) {
            try {
                AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
                    //调用afterPropertiesSet()方法
                    ((InitializingBean) bean).afterPropertiesSet();
                    return null;
                }, getAccessControlContext());
            }
            catch (PrivilegedActionException pae) {
                throw pae.getException();
            }
        }
        else {
            //调用afterPropertiesSet()方法
            ((InitializingBean) bean).afterPropertiesSet();
        }
    }
    if (mbd != null && bean.getClass() != NullBean.class) {
        String initMethodName = mbd.getInitMethodName();
        if (StringUtils.hasLength(initMethodName) &&
            !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
            !mbd.isExternallyManagedInitMethod(initMethodName)) {
            //通过反射的方式调用init-method
            invokeCustomInitMethod(beanName, bean, mbd);
        }
    }
}

分析上述代码后,我们可以初步得出如下信息:

  • Spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件和@Bean注解中通过init-method指定,两种方式可以同时使用。
  • 实现InitializingBean接口是直接调用afterPropertiesSet()方法,比通过反射调用init-method指定的方法效率相对来说要高点。但是init-method方式消除了对Spring的依赖。
  • 如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。

也就是说Spring为bean提供了两种初始化的方式,第一种实现InitializingBean接口,实现afterPropertiesSet方法,第二种配置文件或@Bean注解中通过init-method指定,两种方式可以同时使用,同时使用先调用afterPropertiesSet方法,后执行init-method指定的方法。

DisposableBean接口

1.DisposableBean接口概述

实现org.springframework.beans.factory.DisposableBean接口的bean在销毁前,Spring将会调用DisposableBean接口的destroy()方法。我们先来看下DisposableBean接口的源码,如下所示。

package org.springframework.beans.factory;
public interface DisposableBean {
    void destroy() throws Exception;
}

可以看到,在DisposableBean接口中只定义了一个destroy()方法。

在Bean生命周期结束前调用destory()方法做一些收尾工作,亦可以使用destory-method。前者与Spring耦合高,使用类型强转.方法名(),效率高。后者耦合低,使用反射,效率相对低

2.DisposableBean接口注意事项

多例bean的生命周期不归Spring容器来管理,这里的DisposableBean中的方法是由Spring容器来调用的,所以如果一个多例实现了DisposableBean是没有啥意义的,因为相应的方法根本不会被调用,当然在XML配置文件中指定了destroy方法,也是没有意义的。所以,在多实例bean情况下,Spring不会自动调用bean的销毁方法。

单实例bean案例

创建一个Animal的类实现InitializingBean和DisposableBean接口,代码如下:

package io.mykit.spring.plugins.register.bean;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
/**
 * @author binghe
 * @version 1.0.0
 * @description 测试InitializingBean接口和DisposableBean接口
 */
public class Animal implements InitializingBean, DisposableBean {
    public Animal(){
        System.out.println("执行了Animal类的无参数构造方法");
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("执行了Animal类的初始化方法。。。。。");
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("执行了Animal类的销毁方法。。。。。");
    }
}

接下来,我们新建一个AnimalConfig类,并将Animal通过@Bean注解的方式注册到Spring容器中,如下所示。

package io.mykit.spring.plugins.register.config;
import io.mykit.spring.plugins.register.bean.Animal;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
 * @author binghe
 * @version 1.0.0
 * @description AnimalConfig
 */
@Configuration
@ComponentScan("io.mykit.spring.plugins.register.bean")
public class AnimalConfig {
    @Bean
    public Animal animal(){
        return new Animal();
    }
}

接下来,我们在BeanLifeCircleTest类中新增testBeanLifeCircle02()方法来进行测试,如下所示。

@Test
public void testBeanLifeCircle02(){
    //创建IOC容器
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
    System.out.println("IOC容器创建完成...");
    //关闭IOC容器
    context.close();
}

运行BeanLifeCircleTest类中的testBeanLifeCircle02()方法,输出的结果信息如下所示。

执行了Animal类的无参数构造方法
执行了Animal类的初始化方法。。。。。
IOC容器创建完成...
执行了Animal类的销毁方法。。。。。

从输出的结果信息可以看出:单实例bean下,IOC容器创建完成后,会自动调用bean的初始化方法;而在容器销毁前,会自动调用bean的销毁方法。

多实例bean案例

多实例bean的案例代码基本与单实例bean的案例代码相同,只不过在AnimalConfig类中,我们在animal()方法上添加了@Scope("prototype")注解,如下所示。

package io.mykit.spring.plugins.register.config;
import io.mykit.spring.plugins.register.bean.Animal;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
/**
 * @author binghe
 * @version 1.0.0
 * @description AnimalConfig
 */
@Configuration
@ComponentScan("io.mykit.spring.plugins.register.bean")
public class AnimalConfig {
    @Bean
    @Scope("prototype")
    public Animal animal(){
        return new Animal();
    }
}

接下来,我们在BeanLifeCircleTest类中新增testBeanLifeCircle03()方法来进行测试,如下所示。

@Test
public void testBeanLifeCircle03(){
    //创建IOC容器
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AnimalConfig.class);
    System.out.println("IOC容器创建完成...");
    System.out.println("-------");
    //调用时创建对象
    Object bean = ctx.getBean("animal");
    System.out.println("-------");
    //调用时创建对象
    Object bean1 = ctx.getBean("animal");
    System.out.println("-------");
    //关闭IOC容器
    ctx.close();
}

运行BeanLifeCircleTest类中的testBeanLifeCircle03()方法,输出的结果信息如下所示。

IOC容器创建完成...
-------
执行了Animal类的无参数构造方法
执行了Animal类的初始化方法。。。。。
-------
执行了Animal类的无参数构造方法
执行了Animal类的初始化方法。。。。。
-------

从输出的结果信息中可以看出:在多实例bean情况下,Spring不会自动调用bean的销毁方法。

相关文章
|
25天前
|
Java 开发者 Spring
【SpringBoot 异步魔法】@Async 注解:揭秘 SpringBoot 中异步方法的终极奥秘!
【8月更文挑战第25天】异步编程对于提升软件应用的性能至关重要,尤其是在高并发环境下。Spring Boot 通过 `@Async` 注解简化了异步方法的实现。本文详细介绍了 `@Async` 的基本用法及配置步骤,并提供了示例代码展示如何在 Spring Boot 项目中创建与管理异步任务,包括自定义线程池、使用 `CompletableFuture` 处理结果及异常情况,帮助开发者更好地理解和运用这一关键特性。
101 1
|
21天前
|
缓存 Java 数据库连接
Spring Boot奇迹时刻:@PostConstruct注解如何成为应用初始化的关键先生?
【8月更文挑战第29天】作为一名Java开发工程师,我一直对Spring Boot的便捷性和灵活性着迷。本文将深入探讨@PostConstruct注解在Spring Boot中的应用场景,展示其在资源加载、数据初始化及第三方库初始化等方面的作用。
44 0
|
6天前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
|
6天前
|
Java 数据库连接 Maven
Spring基础1——Spring(配置开发版),IOC和DI
spring介绍、入门案例、控制反转IOC、IOC容器、Bean、依赖注入DI
Spring基础1——Spring(配置开发版),IOC和DI
|
13天前
|
缓存 Java 应用服务中间件
随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架
【9月更文挑战第6天】随着微服务架构的兴起,Spring Boot凭借其快速开发和易部署的特点,成为构建RESTful API的首选框架。Nginx作为高性能的HTTP反向代理服务器,常用于前端负载均衡,提升应用的可用性和响应速度。本文详细介绍如何通过合理配置实现Spring Boot与Nginx的高效协同工作,包括负载均衡策略、静态资源缓存、数据压缩传输及Spring Boot内部优化(如线程池配置、缓存策略等)。通过这些方法,开发者可以显著提升系统的整体性能,打造高性能、高可用的Web应用。
42 2
|
15天前
|
NoSQL 前端开发 Java
使用 Spring Boot + Neo4j 实现知识图谱功能开发
在数据驱动的时代,知识图谱作为一种强大的信息组织方式,正逐渐在各个领域展现出其独特的价值。本文将围绕使用Spring Boot结合Neo4j图数据库来实现知识图谱功能开发的技术细节进行分享,帮助读者理解并掌握这一技术栈在实际项目中的应用。
73 4
|
17天前
|
安全 Java 开发者
强大!Spring Cloud Gateway新特性及高级开发技巧
在微服务架构日益盛行的今天,网关作为微服务架构中的关键组件,承担着路由、安全、监控、限流等多重职责。Spring Cloud Gateway作为新一代的微服务网关,凭借其基于Spring Framework 5、Project Reactor和Spring Boot 2.0的强大技术栈,正逐步成为业界的主流选择。本文将深入探讨Spring Cloud Gateway的新特性及高级开发技巧,助力开发者更好地掌握这一强大的网关工具。
71 6
|
17天前
|
IDE Java 开发工具
还在为繁琐的配置头疼吗?一文教你如何用 Spring Boot 快速启动,让开发效率飙升,从此告别加班——打造你的首个轻量级应用!
【9月更文挑战第2天】Spring Boot 是一款基于 Spring 框架的简化开发工具包,采用“约定优于配置”的原则,帮助开发者快速创建独立的生产级应用程序。本文将指导您完成首个 Spring Boot 项目的搭建过程,包括环境配置、项目初始化、添加依赖、编写控制器及运行应用。首先需确保 JDK 版本不低于 8,并安装支持 Spring Boot 的现代 IDE,如 IntelliJ IDEA 或 Eclipse。
53 5
|
28天前
|
IDE Java 开发工具
快速上手指南:如何用Spring Boot开启你的Java开发之旅?
【8月更文挑战第22天】Spring Boot由Pivotal团队开发,简化了Spring应用的创建过程。本文详述了从零开始搭建Spring Boot项目的步骤:首先确保安装了新版JDK、Maven/Gradle及IDE如IntelliJ IDEA或Eclipse;接着访问Spring Initializr网站(start.spring.io),选择所需依赖(如Web模块)并生成项目;最后,使用IDE打开生成的项目,添加`@SpringBootApplication`注解及main方法来启动应用。通过这些步骤,即便是新手也能快速上手,专注于业务逻辑的实现。
32 1
|
1月前
|
Java 数据安全/隐私保护 Spring
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效