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

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 接口

java 复制代码
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 注解

java 复制代码
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 类:

java 复制代码
public class MyBeanUsingXml {

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

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

然后在 applicationContext.xml 中进行配置:

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 接口

java 复制代码
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;
    }
}

测试代码

java 复制代码
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 的初始化前后进行统一的处理,例如日志记录、属性修改等。
相关推荐
龙茶清欢35 分钟前
5、urbane-commerce 微服务统一依赖版本管理规范
java·运维·微服务
零千叶1 小时前
【面试】Kafka / RabbitMQ / ActiveMQ
面试·kafka·rabbitmq
麻雀20253 小时前
一键面试prompt
面试·职场和发展·prompt
海琴烟Sunshine3 小时前
Leetcode 26. 删除有序数组中的重复项
java·算法·leetcode
RoboWizard3 小时前
移动固态硬盘连接手机无法读取是什么原因?
java·spring·智能手机·电脑·金士顿
PAK向日葵3 小时前
【算法导论】NMWQ 0913笔试题
算法·面试
PAK向日葵3 小时前
【算法导论】DJ 0830笔试题题解
算法·面试
PAK向日葵3 小时前
【算法导论】LXHY 0830 笔试题题解
算法·面试
笨蛋不要掉眼泪3 小时前
SpringBoot项目Excel成绩录入功能详解:从文件上传到数据入库的全流程解析
java·vue.js·spring boot·后端·spring·excel
wshzrf3 小时前
【Java系列课程·Java学前须知】第3课 JDK,JVM,JRE的区别和优缺
java·开发语言·jvm