Java面试宝典:说下Spring Bean的生命周期?

news2025/2/21 9:00:21

Java面试宝典专栏范围:JAVA基础,面向对象编程(OOP),异常处理,集合框架,Java I/O,多线程编程,设计模式,网络编程,框架和工具等全方位面试题详解
每日更新Java面试宝典专栏:Java面试宝典
感兴趣的可以先收藏起来,大家在遇到JAVA面试题等相关问题都可以给我留言咨询,希望帮助更多的人

封面图

回答重点

实例化: Spring 容器根据配置文件或注解实例化 Bean 对象。
属性注入: Spring 将依赖(通过构造器、setter 方法或字段注入)注入到 Bean 实例中。
初始化前的扩展机制: 若 Bean 实现了 BeanNameAware 等 aware 接口,则执行 aware 注入。
初始化前(BeanPostProcessor): 在 Bean 初始化之前,可通过 BeanPostProcessor 接口对 Bean 进行额外处理。
初始化: 调用 InitializingBean 接口的 afterPropertiesSet () 方法或通过 init - method 属性指定的初始化方法。
初始化后(BeanPostProcessor): 在 Bean 初始化后,可通过 BeanPostProcessor 进一步处理。
使用 Bean: Bean 初始化完成后,可被容器中的其他 Bean 使用。
销毁: 当容器关闭时,Spring 调用 DisposableBean 接口的 destroy () 方法或通过 destroy - method 属性指定的销毁方法。

实例化
属性注入
是否实现Aware接口
执行Aware注入
BeanPostProcessor前置处理
是否实现InitializingBean接口或有init - method
执行初始化方法
BeanPostProcessor后置处理
使用Bean
容器关闭
是否实现DisposableBean接口或有destroy - method
执行销毁方法
结束

详细解释

实例化

Spring 容器在启动过程中,会扫描指定的配置文件(如 XML 配置文件)或带有特定注解(如 @Component@Service@Repository@Controller 等)的类。对于 XML 配置,会根据 <bean> 标签的定义来创建 Bean 实例;对于注解方式,容器会利用反射机制来实例化对应的类。例如,当容器扫描到一个标注了 @Service 的类时,会创建该类的对象,这个对象就是一个 Bean 实例。

属性注入

  1. 构造器注入: 通过在目标类中定义带有参数的构造函数,Spring 会根据配置或注解找到对应的依赖对象,然后将这些依赖对象作为参数传递给构造函数来创建 Bean 实例。比如一个 UserService 类依赖 UserRepository,可以在 UserService 的构造函数中接收 UserRepository 作为参数,Spring 会自动将合适的 UserRepository 实例注入进来。
  2. setter 方法注入: 在目标类中为需要注入的属性提供对应的 setter 方法,Spring 会在 Bean 实例化之后,调用 setter 方法将依赖对象注入到属性中。例如,UserService 类有一个 userRepository 属性,提供 setUserRepository 方法,Spring 会调用该方法将 UserRepository 实例设置进去。
  3. 字段注入: 直接在类的字段上使用 @Autowired 等注解,Spring 会自动将匹配的依赖对象注入到该字段中。不过字段注入可能会导致一些测试和依赖管理上的问题,因为它使得依赖关系不够明确。

初始化前的扩展机制

当 Bean 实现了 BeanNameAware 接口时,Spring 容器会在 Bean 实例化之后,在其他初始化操作之前,将当前 Bean 在配置文件或注解中定义的名称注入到 Bean 中。通过实现该接口的 setBeanName 方法,Bean 可以获取自身的名称。类似的,还有 BeanFactoryAware 接口,实现它可以让 Bean 获取到当前的 BeanFactory 实例,从而可以在 Bean 内部对容器进行一些操作,比如获取其他 Bean 等。

初始化前(BeanPostProcessor)

BeanPostProcessor 是 Spring 提供的一个强大的扩展接口。开发者可以自定义实现该接口的类,在其中实现 postProcessBeforeInitialization 方法。当容器创建每个 Bean 实例并完成属性注入后,在调用 Bean 的初始化方法之前,会先调用所有注册的 BeanPostProcessorpostProcessBeforeInitialization 方法,开发者可以在这个方法中对 Bean 进行一些额外的处理,比如修改 Bean 的属性值、动态代理增强等操作。

初始化

  1. 实现 InitializingBean 接口: 当 Bean 实现了 InitializingBean 接口时,在 Bean 的属性注入完成之后,Spring 会调用该接口的 afterPropertiesSet方法。可以在这个方法中编写一些初始化逻辑,比如对一些资源的初始化操作、建立数据库连接等。
  2. 通过 init - method 属性: 在 XML 配置中,可以为 <bean> 标签指定 init - method 属性,其值为 Bean 类中定义的一个无参方法名。Spring 会在属性注入完成后,调用这个指定的方法进行初始化操作。同样,在使用注解配置时,也可以通过 @Bean 注解的 initMethod 属性来指定类似的初始化方法。

初始化后(BeanPostProcessor)

BeanPostProcessorpostProcessAfterInitialization 方法会在 Bean 的初始化方法调用完成之后被调用。在这里可以对已经初始化好的 Bean 进行进一步的处理,比如再次进行动态代理增强、添加一些额外的功能逻辑等。一些框架整合时,会利用这个时机来对 Bean 进行增强以实现特定的功能,比如 AOP 代理的创建等。

使用 Bean

当一个 Bean 完成了上述所有的生命周期步骤,就可以被容器中的其他 Bean 依赖和使用了。其他 Bean 可以通过属性注入等方式获取到这个 Bean 的实例,然后调用其提供的方法来完成业务逻辑。例如,OrderService 依赖 UserServiceUserService 已经完成初始化,OrderService 可以通过构造器注入获取到 UserService 实例,进而调用 UserService 中的方法来处理订单相关业务中涉及用户的操作。

销毁

  1. 实现 DisposableBean 接口: 当 Bean 实现了 DisposableBean 接口时,在 Spring 容器关闭时,会调用该接口的 destroy 方法。可以在这个方法中编写一些资源释放的逻辑,比如关闭数据库连接、释放文件句柄等。
  2. 通过 destroy - method 属性: 类似于初始化方法,在 XML 配置中可以为 <bean> 标签指定 destroy - method 属性,其值为 Bean 类中定义的一个无参方法名。Spring 容器关闭时,会调用这个指定的方法来进行资源清理等销毁操作。在注解配置中,也可以通过 @Bean 注解的 destroyMethod 属性来指定销毁方法。

Bean 的作用域(Scope)与生命周期的关系

Spring Bean 的生命周期还与其作用域密切相关:

  • Singleton(单例): 默认作用域,Bean 的生命周期与 Spring 容器的生命周期一致。在容器启动时创建,在容器关闭时销毁。
  • Prototype(原型): 每次请求时创建一个新的 Bean 实例,容器只负责创建,不管理其生命周期(不调用销毁方法)。
  • Request、Session、Application、WebSocket: 这些作用域用于 Web 应用中,Bean 的生命周期分别与 HTTP 请求、会话、应用或 WebSocket 的生命周期一致。

常见的生命周期场景有哪些?

数据库连接初始化与关闭

在一个 Web 应用中,经常需要与数据库进行交互。我们可以创建一个DatabaseConnection的 Bean。

  • 实例化与属性注入: Spring 容器启动时,根据配置实例化DatabaseConnection对象,并注入数据库连接所需的配置信息,比如数据库地址、用户名、密码等。
  • 初始化: 可以实现InitializingBean接口,在afterPropertiesSet方法中编写代码来建立实际的数据库连接,比如使用 JDBC 驱动来获取一个数据库连接对象。
  • 使用: 其他业务逻辑相关的 Bean,比如UserRepository,可以通过属性注入获取DatabaseConnection实例,然后利用这个连接来执行 SQL 语句,查询、插入、更新或删除数据。
  • 销毁: 当应用关闭时,实现DisposableBean接口,在destroy方法中关闭数据库连接,释放相关资源,避免资源泄漏。

日志记录器配置

很多应用都需要记录日志来跟踪程序运行情况。我们可以创建一个LoggerBean

  • 实例化与属性注入: Spring 容器启动时创建LoggerBean实例,并注入日志相关的配置,例如日志级别、日志文件路径等。
  • 初始化:LoggerBean中可以实现InitializingBean接口或者通过init - method指定一个初始化方法。在这个方法里,根据注入的配置信息来初始化日志记录器,比如设置日志输出格式,加载日志配置文件等。
  • 使用: 应用中的其他 Bean,比如OrderService,可以注入LoggerBean,在业务方法中调用LoggerBean的方法来记录日志,例如在处理订单时记录订单创建时间、订单状态变更等信息。
  • 销毁: 应用关闭时,LoggerBean的销毁方法(如果实现了DisposableBean接口或者配置了destroy - method)可以执行一些清理工作,比如关闭日志文件等。

消息队列生产者初始化

在分布式系统中,经常会使用消息队列来进行异步通信。假设有一个MessageProducer的 Bean 用于向消息队列发送消息。

  • 实例化与属性注入: Spring 容器启动时实例化MessageProducer,并注入消息队列的连接信息,如消息队列服务器地址、端口等,以及一些生产者相关的配置,比如消息持久化策略等。
  • 初始化: 可以通过实现InitializingBean接口,在afterPropertiesSet方法中建立与消息队列服务器的连接,创建消息生产者对象,并进行一些必要的配置初始化。
  • 使用: 业务 Bean,比如ProductService,当有新商品发布等事件发生时,可以注入MessageProducer,调用其方法将相关消息发送到消息队列中,以便其他系统或组件进行处理。
  • 销毁: 当应用关闭时,MessageProducer的销毁方法可以关闭与消息队列的连接,释放资源,确保系统正常退出。

在Spring中,如何自定义一个Bean的生命周期?

下面将通过多种方式来展示在 Spring 中自定义 Bean 生命周期的具体 Java 代码示例,包含使用接口、注解以及 XML 配置等方式。

1. 实现 InitializingBean 和 DisposableBean 接口

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

// 使用 @Component 注解将该类注册为 Spring Bean
@Component
public class MyBeanUsingInterfaces implements InitializingBean, DisposableBean {

    // 实现 InitializingBean 接口的 afterPropertiesSet 方法,用于初始化操作
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("MyBeanUsingInterfaces: Initializing...");
    }

    // 实现 DisposableBean 接口的 destroy 方法,用于销毁操作
    @Override
    public void destroy() throws Exception {
        System.out.println("MyBeanUsingInterfaces: Destroying...");
    }
}

2. 使用 @PostConstruct 和 @PreDestroy 注解

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;

// 使用 @Component 注解将该类注册为 Spring Bean
@Component
public class MyBeanUsingAnnotations {

    // 使用 @PostConstruct 注解,在 Bean 属性注入完成后执行初始化操作
    @PostConstruct
    public void init() {
        System.out.println("MyBeanUsingAnnotations: Initializing...");
    }

    // 使用 @PreDestroy 注解,在 Spring 容器关闭时执行销毁操作
    @PreDestroy
    public void destroy() {
        System.out.println("MyBeanUsingAnnotations: Destroying...");
    }
}

3. 在 XML 配置文件中指定 init-method 和 destroy-method 属性

首先创建一个 Java 类:

public class MyBeanUsingXml {

    // 初始化方法
    public void init() {
        System.out.println("MyBeanUsingXml: Initializing...");
    }

    // 销毁方法
    public void destroy() {
        System.out.println("MyBeanUsingXml: Destroying...");
    }
}

然后在 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">

    <!-- 定义 MyBeanUsingXml Bean,并指定初始化和销毁方法 -->
    <bean id="myBeanUsingXml" class="com.example.MyBeanUsingXml"
          init-method="init" destroy-method="destroy"/>
</beans>

4. 实现 BeanPostProcessor 接口

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

// 使用 @Component 注解将该类注册为 Spring Bean
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    // 在 Bean 初始化前执行的方法
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Before Initialization: " + beanName);
        return bean;
    }

    // 在 Bean 初始化后执行的方法
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("After Initialization: " + beanName);
        return bean;
    }
}

测试代码

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    public static void main(String[] args) {
        // 对于使用注解的方式
        AnnotationConfigApplicationContext annotationContext = new AnnotationConfigApplicationContext();
        annotationContext.scan("com.example");
        annotationContext.refresh();

        // 获取并使用 Bean
        MyBeanUsingInterfaces myBeanUsingInterfaces = annotationContext.getBean(MyBeanUsingInterfaces.class);
        MyBeanUsingAnnotations myBeanUsingAnnotations = annotationContext.getBean(MyBeanUsingAnnotations.class);

        // 关闭容器,触发销毁方法
        annotationContext.close();

        // 对于使用 XML 配置的方式
        ClassPathXmlApplicationContext xmlContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyBeanUsingXml myBeanUsingXml = xmlContext.getBean("myBeanUsingXml", MyBeanUsingXml.class);
        xmlContext.close();
    }
}

代码解释

  • 实现接口方式: 通过实现 InitializingBeanDisposableBean 接口,Spring 会自动调用相应的方法来完成初始化和销毁操作。
  • 注解方式: 使用 @PostConstruct@PreDestroy 注解,让代码更简洁,Spring 会在合适的时机调用被注解的方法。
  • XML 配置方式: 在 XML 中指定 init - methoddestroy - method,Spring 会根据配置调用对应的方法。
  • BeanPostProcessor 方式: 可以在所有 Bean 的初始化前后进行统一的处理,例如日志记录、属性修改等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2298506.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

early bird inject

基本原理 本质是利用windows系统的apc机制&#xff0c;以及涉及到windows进程启动的流程. 因为线程初始化阶段LdrInitializeThunk函数会调用NtTestAlert函数,这个函数执行后,所有apc队列中的例程都会执行.因此我们在主线程初始化之前向主线程的apc队列中加入恶意代码即可实现…

uvm错误记录4

如下所示&#xff0c;奇怪的是penable莫名其妙的出X。可问题&#xff0c;我发送激励了。 仔细定位发现&#xff0c;39行用的是vif中的penable, 问题是都是赋值&#xff0c;却出现同时赋值多次&#xff0c;这是因为nonblocking和blocking同时触发导致的&#xff0c;因此&#xf…

3dtiles——Cesium ion for Autodesk Revit Add-In插件

一、说明&#xff1a; Cesium已经支持3dtiles的模型格式转换&#xff1b; 可以从Cesium官方Aesset中上传gltf等格式文件转换为3dtiles&#xff1b; 也可以下载插件&#xff08;例如revit-cesium插件&#xff09;转换并自动上传到Cesium官方Aseet中。 Revit转3dtiles插件使用…

QT 异步编程之多线程

一、概述 1、在进行桌面应用程序开发的时候&#xff0c;假设应用程序在某些情况下需要处理比较复制的逻辑&#xff0c;如果只有一个线程去处理&#xff0c;就会导致窗口卡顿&#xff0c;无法处理用户的相关操作。这种情况下就需要使用多线程&#xff0c;其中一个线程处理窗口事…

Proxmox 更新软件包数据库(TASK ERROR: command ‘apt-get update‘ failed: exit code 100)

1、连接自己报错的物理机Shell&#xff0c;编辑文件 vi /etc/apt/sources.list.d/pve-enterprise.list 2、注释文件的第一行在开头加上# 按I进入编辑模式后 开头添加# 然后shift&#xff1a; 输入wq或者wq&#xff01;进行保存 3、注释后执行两个命令apt-get update 和 apt…

JVM——垃圾回收算法

目录 垃圾回收算法 评价标准&#xff1a; 标记-清除算法&#xff1a; 复制算法&#xff1a; 标记-整理算法&#xff1a; 分代GC&#xff1a; arthas查看分代之后的内存情况&#xff1a; 垃圾回收算法 java是如何实现垃圾回收的呢&#xff1f;简单来说&#xff0c;垃圾回…

服务器安全——日志分析和扫描

如何通过访问日志查询被攻击 扫描攻击 攻击日志 GET /index?sindex/%5Cthink%5CModule/Action/Param/$%7Bphpinfo()%7D HTTP/1.1", host: "主机", referrer: "主机sindex/\think\Module/Action/Param/${phpinfo()}" 攻击日志文件 .error.log sql注…

ubuntu 22.04 安装vsftpd服务

先决条件&#xff0c;确保你已经配置好了存储库。 安装vsftpd 为了方便实验&#xff0c;我已经切换到了root用户。 rootlocal:~# apt-get install vsftpd修改配置 配置文件在 /etc/vsftpd.conf rootlocal:~# grep -vE ^#|^$ /etc/vsftpd.conf listenNO listen_ipv6YES anonymou…

STM32F407通过FSMC扩展外部SRAM和NAND FLASH

1 扩展外部SRAM 1.1 地址情况 FSMC控制器的存储区分为4个区(Bank)&#xff0c;每个区256MB。其中&#xff0c;Bank1可以用于连接SRAM、NOR FLASH、PSRAM&#xff0c;还可以连接TFT LCD。Bank1的地址范围是0x60000000&#xff5e;0x6FFFFFFF。Bank1又分为4个子区&#xff0c;每…

AndroidStudio查看Sqlite和SharedPreference

1.查看Sqlite 使用App Inspection&#xff0c;这是个好东西 打开方式&#xff1a;View → Tool Windows → App Inspection 界面如图&#xff1a; App inspection不但可以看Sqlite还可以抓包network和background task连抓包工具都省了。 非常好使 2.查看sharedPreference 使…

Elasticsearch:15 年来致力于索引一切,找到重要内容

作者&#xff1a;来自 Elastic Shay Banon 及 Philipp Krenn Elasticsearch 刚刚 15 岁了&#xff01;回顾过去 15 年的索引和搜索&#xff0c;并展望未来 15 年的相关内容。 Elasticsearch 刚刚成立 15 周年。一切始于 2010 年 2 月的一篇公告博客文章&#xff08;带有标志性的…

信呼OA办公系统sql注入漏洞分析

漏洞描述 信呼OA办公系统uploadAction存在SQL注入漏洞&#xff0c;攻击者可利用该漏洞获取数据库敏感信息。 环境搭建 源码下载地址&#xff1a;https://github.com/rainrocka/xinhu 下载后解压到本地网站根目录下&#xff0c;配置好数据库&#xff0c;然后安装即可 默认密…

机器学习算法 - 随机森林之决策树初探(1)

随机森林是基于集体智慧的一个机器学习算法&#xff0c;也是目前最好的机器学习算法之一。 随机森林实际是一堆决策树的组合&#xff08;正如其名&#xff0c;树多了就是森林了&#xff09;。在用于分类一个新变量时&#xff0c;相关的检测数据提交给构建好的每个分类树。每个…

原生Three.js 和 Cesium.js 案例 。 智慧城市 数字孪生常用功能列表

对于大多数的开发者来言&#xff0c;看了很多文档可能遇见不到什么有用的&#xff0c;就算有用从文档上看&#xff0c;把代码复制到自己的本地大多数也是不能用的&#xff0c;非常浪费时间和学习成本&#xff0c; 尤其是three.js &#xff0c; cesium.js 这种难度较高&#xff…

在 PyCharm 中接入deepseek的API的各种方法

在 PyCharm 中接入 DeepSeek 的 API&#xff0c;通常需要以下步骤&#xff1a; 1. 获取 DeepSeek API 密钥 首先&#xff0c;确保你已经在 DeepSeek 平台上注册并获取了 API 密钥&#xff08;API Key&#xff09;。如果没有&#xff0c;请访问 DeepSeek 的官方网站注册并申请 …

【2025新】基于springboot的问卷调查小程序设计与实现

目录 一、整体目录&#xff08;示范&#xff09;&#xff1a; 文档含项目技术介绍、E-R图、数据字典、项目功能介绍与截图等 二、运行截图 三、代码部分&#xff08;示范&#xff09;&#xff1a; 四、数据库表(示范)&#xff1a; 数据库表有注释&#xff0c;可以导出数据…

数据结构——Makefile、算法、排序(2025.2.13)

目录 一、Makefile 1.功能 2.基本语法和相关操作 &#xff08;1&#xff09;创建Makefile文件 &#xff08;2&#xff09;编译规则 &#xff08;3&#xff09;编译 &#xff08;4&#xff09;变量 ①系统变量 ②自定义变量 二、 算法 1.定义 2.算法的设计 &#xff…

什么是Docker多架构容器镜像

什么是Docker多架构容器镜像 在 Docker 中&#xff0c;同一个 Docker 镜像可以在不同的平台上运行&#xff0c;例如在 x86、ARM、PowerPC 等不同的 CPU 架构上。 为了支持这种多平台的镜像构建和管理&#xff0c;Docker 在 17.06 版本时引入了 Manifest 的概念&#xff0c;在…

【devops】 Git仓库如何fork一个私有仓库到自己的私有仓库 | git fork 私有仓库

一、场景说明 场景&#xff1a; 比如我们Codeup的私有仓库下载代码 放入我们的Github私有仓库 且保持2个仓库是可以实现fork的状态&#xff0c;即&#xff1a;Github会可以更新到Codeup的最新代码 二、解决方案 1、先从Codeup下载私有仓库代码 下载代码使用 git clone 命令…

RocketMQ及和Kafka的区别

目录 1 从场景入手2 RocketMQ是什么&#xff1f;3 RocketMQ及和Kafka的区别3.1 在架构上做了减法3.1.1 简化协调节点3.1.2 简化分区3.1.3 底层存储3.1.3.1 Kafka底层存储3.1.3.1 RocketMQ底层存储 3.1.4 简化备份模型3.1.4.1 Kafka备份模型3.1.4.2 RocketMQ备份模型 3.1.5 Rock…