5. [进阶配置] Bean 的高级管理

5. [进阶配置] Bean 的高级管理

摘要: 掌握了基础的 DI 配置后,本章我们将深入探索 Bean 的更多高级特性。我们将学习 Bean 的作用域如何影响其实例数量,Bean 的完整生命周期流程,以及 Spring 创建 Bean 的多种底层方式,包括强大的 FactoryBean。


5.1. Bean 的作用域 (Scope)

在 Spring 中,作用域 (Scope) 决定了 Spring IoC 容器如何创建和管理 Bean 的实例。简单来说,它回答了这样一个问题:“当我从容器中请求一个 Bean 时,是每次都给我一个新的对象,还是始终给我同一个?”。正确地使用作用域对于应用的性能和状态管理至关重要。

Spring 定义了多种作用域,但最核心、最常用的只有两种:singleton (单例) 和 prototype (原型/多例)。

5.1.1. 详解 singleton:唯一的实例

singleton 是 Spring 默认 的作用域。当一个 Bean 的作用域为 singleton 时,无论你从容器中获取多少次该 Bean,Spring 容器都 只会返回同一个共享的实例

1. 特性与创建时机

  • 唯一实例: 在整个 Spring IoC 容器的生命周期内,一个 Bean ID 只对应一个对象实例。
  • 创建时机: 默认情况下,singleton 作用域的 Bean 在 容器启动和初始化时 就会被创建并放入一个缓存区(俗称“单例池”),等待后续的注入和调用。

2. 实践验证

我们在 spring6 项目中进行验证。

文件路径: src/main/java/com/example/spring6/bean/SpringBean.java (新增文件)

1
2
3
4
5
6
7
package com.example.spring6.bean;

public class SpringBean {
public SpringBean() {
System.out.println("SpringBean 的无参数构造方法执行了...");
}
}

文件路径: src/main/resources/spring-scope.xml (新增配置文件)

1
2
3
4
5
6
7
8
<?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">

<bean id="sb" class="com.example.spring6.bean.SpringBean"/>

</beans>

测试代码:
文件路径: src/test/java/com/example/spring6/test/ScopeTest.java (新增测试类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.example.spring6.test;

import com.example.spring6.bean.SpringBean;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ScopeTest {
@Test
public void testScope() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
SpringBean sb1 = applicationContext.getBean("sb", SpringBean.class);
System.out.println("sb1 = " + sb1);
SpringBean sb2 = applicationContext.getBean("sb", SpringBean.class);
System.out.println("sb2 = " + sb2);


}
@Test
public void testSingletonCreateTiming() {
// 仅创建容器,不获取 Bean
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-scope.xml");
}

}

1
2
3
SpringBean 的无参数构造方法执行了...
sb1 = com.example.spring6.bean.SpringBean@2c88b9fc
sb2 = com.example.spring6.bean.SpringBean@2c88b9fc

结果分析: 构造方法只执行了一次,并且两次获取到的对象地址是完全相同的,证明了 singleton 的唯一性。

运行 testSingletonCreateTiming 测试:

1
SpringBean 的无参数构造方法执行了...

结果分析: 即使我们没有调用 getBean(),构造方法依然在容器初始化时就被执行了,证明了其“饿汉式”的创建时机。


5.1.2. 详解 prototype:多变的实例

singleton 相对,prototype 作用域的 Bean 每次被请求时,Spring 容器都会 创建一个全新的实例 返回。

1. 特性与创建时机

  • 全新实例: 每一次 getBean() 调用或每一次注入操作,都会触发一次全新的对象创建过程。
  • 创建时机: prototype 作用域的 Bean 是 懒加载 的。只有当它被实际请求(getBean())时,容器才会去创建它的实例。
  • 生命周期管理: Spring 容器在创建并初始化 prototype Bean 后,就会将其交给调用方,不再追踪其后续的生命周期。这意味着 Spring 不会为 prototype Bean 调用其销毁方法 (destroy-method)。

2. 实践验证

文件路径: src/main/resources/spring-scope.xml (修改)

1
<bean id="sb" class="com.example.spring6.bean.SpringBean" scope="prototype"/>

测试代码: 使用与上面完全相同的 ScopeTest.java

运行 testSingletonScope 测试 (现在 sb Bean 是 prototype):

1
2
3
4
SpringBean 的无参数构造方法执行了...
sb1 = com.example.spring6.bean.SpringBean@5316e95f
SpringBean 的无参数构造方法执行了...
sb2 = com.example.spring6.bean.SpringBean@3f053c80

结果分析: 构造方法被执行了两次,并且两次获取到的对象地址是不同的,证明了 prototype 的多例性。

运行 testSingletonCreateTiming 测试 (现在 sb Bean 是 prototype):

1
(无任何输出)

结果分析: 仅创建容器而没有 getBean() 时,构造方法完全没有被执行,证明了其“懒加载”的创建时机。


5.1.3. Spring Boot 对比:使用 @Scope 注解

在 Spring Boot 中,我们使用 @Scope 注解来声明 Bean 的作用域,这比 XML 配置更加直观和便捷。

通过在 <bean> 标签上设置 scope 属性来定义作用域。

1
2
3
<bean id="singletonBean" class="com.example.spring6.bean.SpringBean" />

<bean id="prototypeBean" class="com.example.spring6.bean.SpringBean" scope="prototype"/>

通过在 Bean 的声明注解(如 @Component, @Service)之上添加 @Scope 注解来定义作用域。

Java 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// SpringBean.java
package com.example.spring6.bean;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component // 1. 首先,它必须是一个被 Spring 管理的 Bean
@Scope("prototype") // 2. 使用 @Scope 注解指定其作用域为 prototype
public class SpringBean {
public SpringBean() {
// 为了方便观察,每次创建都打印线程名
System.out.println("SpringBean constructor executed by thread: " + Thread.currentThread().getName());
}
}

5.2. Bean 的实例化方式

我们知道,Spring 创建 Bean 最常规的方式是调用其无参构造函数。但在真实的开发世界里,对象的创建过程远比 new User() 要复杂得多。本节,我们将聚焦于那些“非常规”的实例化场景,并探索 Spring 是如何通过多种灵活的机制来优雅地应对它们的。

5.2.1. 构造方法实例化 (回顾)

这是标准场景,我们不再赘述。当一个类拥有公开的无参构造器时,Spring 默认通过它来创建实例。

1
<bean id="userBean" class="com.example.spring6.bean.User"/>

5.2.2. 静态工厂方法 (factory-method)

“痛点”场景:

假设我们需要集成一个公司内部的遗留工具库。这个库中有一个 LegacyApiClient 类,它的构造函数是 private 的,我们无法直接 new 它。幸运的是,这个类提供了一个静态方法 public static LegacyApiClient getInstance() 来获取其单例对象。那么,我们如何在 Spring XML 中配置并管理这个由静态方法创建的对象呢?

解决方案:
Spring 提供了 factory-method 属性,专门用于调用一个静态方法来创建 Bean 实例。

1. 准备代码 (模拟遗留库)

文件路径: src/main/java/com/example/spring6/bean/LegacyApiClient.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.spring6.bean;

// 模拟一个无法直接 new 的遗留类
public class LegacyApiClient {
// 构造器是私有的
private LegacyApiClient() {
System.out.println("遗留 API 客户端实例已创建...");
}

// 提供一个公共的静态工厂方法
public static LegacyApiClient getInstance() {
return new LegacyApiClient();
}
}

2. 配置与测试

XML 配置: src/main/resources/spring-instantiation.xml (新增配置文件)

1
2
3
4
5
6
7
8
9
10
11
12
<?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">

<!-- LegacyApiClient.getInstance() 是一个 public static 方法 -->
<bean id="apiClient"
class="com.example.spring6.bean.LegacyApiClient"
factory-method="getInstance"/>

</beans>

测试代码:

1
2
3
4
5
6
@Test
public void testStaticFactory() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-instantiation.xml");
LegacyApiClient client = context.getBean("apiClient", LegacyApiClient.class);
System.out.println(client);
}

结论: 我们成功地让 Spring 通过调用 LegacyApiClient.getInstance() 方法,将这个原本无法直接实例化的对象纳入了容器管理。


5.2.3. 实例工厂方法 (factory-bean)

“痛点”场景:

现在情况变得更复杂。我们需要一个数据库连接池 ConnectionPool 对象,但这个对象不能直接 new,必须由一个 ConnectionPoolManager 的实例来创建。更关键的是,这个 manager 对象本身在创建 pool 之前,需要先进行配置(比如设置最大连接数 maxConnections)。我们如何让 Spring 先创建并配置好 manager,然后再调用这个配置好的 manager 实例的 createPool() 方法来创建 pool Bean 呢?

解决方案:
这就是 factory-bean 属性的用武之地。它允许我们指定一个已经存在的 Bean 实例作为工厂,来创建另一个 Bean。

1. 准备代码

文件路径: src/main/java/com/example/spring6/bean/ConnectionPool.java

1
2
3
package com.example.spring6.bean;
// 模拟的连接池产品
public class ConnectionPool {}

文件路径: src/main/java/com/example/spring6/bean/ConnectionPoolManager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.spring6.bean;

// 模拟的连接池管理器(实例工厂)
public class ConnectionPoolManager {
private int maxConnections;

public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}

// 实例方法,负责创建 ConnectionPool 实例
public ConnectionPool createPool() {
System.out.println("实例工厂 ConnectionPoolManager 正在创建连接池,最大连接数:" + this.maxConnections);
return new ConnectionPool();
}
}

2. 配置与测试

XML 配置: src/main/resources/spring-instantiation.xml (修改)

1
2
3
4
5
6
7
<bean id="poolManager" class="com.example.spring6.bean.ConnectionPoolManager">
<property name="maxConnections" value="10"/>
</bean>

<bean id="connectionPool"
factory-bean="poolManager"
factory-method="createPool"/>

测试代码:

1
2
3
4
5
6
@Test
public void testInstanceFactory() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-instantiation.xml");
ConnectionPool pool = context.getBean("connectionPool", ConnectionPool.class);
System.out.println(pool);
}

5.2.4. FactoryBean 接口:一种特殊的工厂 Bean

“痛点”场景:

在项目中,我们可能需要根据配置文件中的一个 type 值(例如 ‘simple’ 或 ‘complex’)来决定创建一个 SimpleMessageConverter 还是 ComplexMessageConverter。这个创建逻辑如果散落在业务代码中会非常混乱。我们希望将这种复杂的、有条件的创建逻辑封装成一个可重用的 Spring 组件,让这个“工厂”本身可以被配置,并由 Spring 来调用它生产最终的 Bean。

解决方案:
Spring 提供了一个完美的、框架原生的解决方案:实现 FactoryBean 接口。它允许我们将复杂的实例化逻辑封装在一个类中,使其成为一个专门为 Spring 生产 Bean 的“工厂 Bean”。

1. 准备代码

文件路径: src/main/java/com/example/spring6/bean/MessageConverter.java

1
2
3
4
5
6
7
package com.example.spring6.bean;

// 产品接口
public interface MessageConverter {}
// 两种具体实现
class SimpleMessageConverter implements MessageConverter {}
class ComplexMessageConverter implements MessageConverter {}

文件路径: src/main/java/com/example/spring6/bean/MessageConverterFactoryBean.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.example.spring6.bean;
import org.springframework.beans.factory.FactoryBean;

// 1. 实现 FactoryBean 接口
public class MessageConverterFactoryBean implements FactoryBean<MessageConverter> {

// 这个工厂本身可以被配置
private String type = "simple";

public void setType(String type) {
this.type = type;
}

// 2. 封装复杂的创建逻辑在 getObject() 方法中
@Override
public MessageConverter getObject() throws Exception {
System.out.println("FactoryBean 正在根据 type='" + type + "' 创建 MessageConverter...");
if ("simple".equalsIgnoreCase(type)) {
return new SimpleMessageConverter();
} else if ("complex".equalsIgnoreCase(type)) {
return new ComplexMessageConverter();
}
throw new IllegalArgumentException("未知的转换器类型: " + type);
}

@Override
public Class<?> getObjectType() {
return MessageConverter.class;
}
}

2. 配置与测试

XML 配置: src/main/resources/spring-instantiation.xml (修改)

1
2
3
<bean id="messageConverter" class="com.example.spring6.bean.MessageConverterFactoryBean">
<property name="type" value="complex"/>
</bean>

测试代码:

1
2
3
4
5
6
7
8
@Test
public void testFactoryBean() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-instantiation.xml");
// 当我们获取 id 为 "messageConverter" 的 Bean 时...
MessageConverter converter = context.getBean("messageConverter", MessageConverter.class);
// ...Spring 返回的不是 FactoryBean 本身,而是它 getObject() 方法的返回值!
System.out.println("获取到的 Bean 类型: " + converter.getClass().getSimpleName());
}

5.2.5. Spring Boot 对比:使用 @Configuration@Bean 方法

“痛点”场景:

我们已经看到了 XML 中各种工厂方法的威力,但 XML 配置是字符串形式的,类型不安全(写错类名或方法名在编译期无法发现),难以重构(IDE 无法方便地查找引用和重命名),而且配置和逻辑是分离的。在 Spring Boot 中,有没有一种既能实现所有这些复杂创建逻辑,又具备 Java 代码所有优点的现代方式呢?

解决方案:
当然有!这就是 Spring Boot 的核心配置机制:@Configuration 类和 @Bean 方法。它允许我们用纯 Java 代码来定义和配置 Bean。

  • 静态工厂:
    1
    <bean class="...Factory" factory-method="get"/>
  • 实例工厂:
    1
    2
    <bean id="myFactory" class="...Factory"/>
    <bean factory-bean="myFactory" factory-method="get"/>
  • FactoryBean:
    1
    <bean id="product" class="...ProductFactoryBean"/>

配置思路解读:
我们创建一个被 @Configuration 注解的类,这个类就相当于一个 XML 配置文件。类中所有被 @Bean 注解的方法,其返回值都会被 Spring 注册为一个 Bean,方法名默认就是 Bean 的 ID。

Java 配置: src/main/java/com/example/spring6boot/config/AppConfig.java (新增文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.example.spring6boot.config;

import com.example.spring6.bean.*; // 引入我们之前定义的类
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // 声明这是一个配置类,替代 XML 文件
public class AppConfig {

// 等价于【静态工厂方法】
@Bean
public LegacyApiClient apiClient() {
return LegacyApiClient.getInstance();
}

// 等价于【实例工厂方法】
@Bean
public ConnectionPoolManager poolManager(@Value("${pool.maxConnections:20}") int max) {
// 方法参数可以注入配置值或其它 Bean
ConnectionPoolManager manager = new ConnectionPoolManager();
manager.setMaxConnections(max); // 编程方式进行配置
return manager;
}
@Bean
public ConnectionPool connectionPool(ConnectionPoolManager manager) { // 自动注入上面定义的 manager
return manager.createPool();
}

// 等价于【FactoryBean】
// 我们可以用纯 Java 逻辑实现更复杂的创建过程
@Bean
public MessageConverter messageConverter(@Value("${converter.type:simple}") String type) {
System.out.println("@Bean 正在根据 type='" + type + "' 创建 MessageConverter...");
if ("simple".equalsIgnoreCase(type)) {
return new SimpleMessageConverter();
} else {
return new ComplexMessageConverter();
}
}
}

对比总结: Spring Boot 的 Java 配置方式 (@Configuration + @Bean) 完胜 XML 配置。它提供了完全的类型安全(所有东西都是 Java 代码,编译器会检查错误)、更好的可重构性(可以利用 IDE 的全部功能),并且能以编程方式实现任意复杂的 Bean 创建逻辑。这是现代 Spring 应用配置 Bean 的标准和推荐方式。


5.3. Bean 的生命周期

一个 Bean 在 Spring IoC 容器中,从被创建到最终被销毁,会经历一系列预定义的阶段,这就是 Bean 的生命周期。理解这个过程至关重要,因为它为我们提供了在特定时间点介入、执行自定义逻辑的机会。

“痛点”场景:

假设我们有一个 DatabaseManager Bean,它负责管理数据库连接。我们必须确保,在它所有必需的属性(如 url, username)被 Spring 注入之后,立刻调用 openConnection() 方法来建立连接;而在整个应用关闭、容器销毁这个 Bean 之前,必须调用 closeConnection() 方法来安全地释放资源。我们如何才能将这两个关键操作精确地安插在这两个时间点呢?

解决方案:
Spring 强大的生命周期回调机制,正是为了解决这类问题而设计的。它允许我们指定特定的方法,在 Bean 初始化和销毁时自动执行。

5.3.1. 核心五步:从实例化到销毁

我们可以将一个 Bean 的生命周期粗略地划分为五个核心阶段,使用时间线来展示最为清晰:

bean生命周期

Instantiation

Spring 容器通过构造方法创建 Bean 的实例。

Populate Properties

Spring 容器通过 DI 为 Bean 的属性注入值。

Initialization

执行开发者自定义的初始化逻辑,例如 openConnection()

In Use

Bean 处于可用状态,可以被应用程序中的其他对象调用。

Destruction

容器关闭时,执行自定义的销毁逻辑,例如 closeConnection()

实践验证

文件路径: src/main/java/com/example/spring6/bean/LifecycleBean.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.example.spring6.bean;

public class LifecycleBean {
private String name;

// 对应生命周期第 1 步
public LifecycleBean() {
System.out.println("1. 实例化 Bean (调用构造器)");
}

// 对应生命周期第 2 步
public void setName(String name) {
this.name = name;
System.out.println("2. Bean 属性赋值 (调用 setter)");
}

// 对应生命周期第 3 步 (自定义初始化方法)
public void init() {
System.out.println("3. 初始化 Bean (调用自定义 init 方法)");
}

// 对应生命周期第 5 步 (自定义销毁方法)
public void destroy() {
System.out.println("5. 销毁 Bean (调用自定义 destroy 方法)");
}
}

文件路径: src/main/resources/spring-lifecycle.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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">

<bean id="lifecycleBean"
class="com.example.spring6.bean.LifecycleBean"
init-method="init"
destroy-method="destroy">
<property name="name" value="TestBean"/>
</bean>

</beans>

文件路径: src/test/java/com/example/spring6/test/LifecycleTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.example.spring6.test;

import com.example.spring6.bean.LifecycleBean;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class LifecycleTest {
@Test
public void testLifecycleFiveSteps() {
// 要想触发销毁方法,必须使用 ClassPathXmlApplicationContext 并手动 close()
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-lifecycle.xml");

LifecycleBean bean = context.getBean("lifecycleBean", LifecycleBean.class);
System.out.println("4. 使用 Bean");

// 关键:手动关闭容器,以触发 Bean 的销毁阶段
context.close();
}
}
1
2
3
4
5
1. 实例化 Bean (调用构造器)
2. Bean 属性赋值 (调用 setter)
3. 初始化 Bean (调用自定义 init 方法)
4. 使用 Bean
5. 销毁 Bean (调用自定义 destroy 方法)

结论: 通过 init-methoddestroy-method,我们成功地将自定义逻辑挂载到了 Bean 的初始化和销毁阶段。


5.3.2. 关键扩展点:BeanPostProcessor

“痛点”场景:

init-method 很好用,但它只能对单个 Bean 生效。如果我们想对容器中所有(或某一批)的 Bean,在它们各自的 init 方法执行前后,都统一执行一段逻辑(比如打印日志、创建代理对象等),难道要修改每一个 Bean 的配置吗?这显然不现实。

解决方案:
这是一种典型的“横切关注点”,Spring 提供了其体系中最强大的扩展点之一:Bean 后置处理器 (BeanPostProcessor)

加入后置处理器后,生命周期演变为更精细的七个步骤:

  • 1.创建 Bean 实例。
  • 2.为 Bean 注入依赖。
  • 3.Bean后置处理器前置处理init-method 执行前的第一个扩展点。
  • 4.执行 Bean 自定义的 init-method
  • 5.Bean后置处理器后置处理init-method 执行后的第二个扩展点,常用于创建代理对象。
  • 6.Bean 处于可用状态。
  • 7.执行 Bean 自定义的 destroy-method

实践验证

文件路径: src/main/java/com/example/spring6/bean/LogBeanPostProcessor.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.example.spring6.bean;

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

public class LogBeanPostProcessor implements BeanPostProcessor {
// 在每个 Bean 的【初始化方法之前】执行
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("-> postProcessBeforeInitialization: " + beanName);
return bean; // 必须返回 bean,否则后续流程会拿到 null
}

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

文件路径: src/main/resources/spring-lifecycle.xml (修改)

1
<bean class="com.example.spring6.bean.LogBeanPostProcessor"/>

再次运行上一节的 testLifecycleFiveSteps() 测试。

1
2
3
4
5
6
7
1. 实例化 Bean (调用构造器)
2. Bean 属性赋值 (调用 setter)
-> postProcessBeforeInitialization: lifecycleBean
3. 初始化 Bean (调用自定义 init 方法)
-> postProcessAfterInitialization: lifecycleBean
4. 使用 Bean
5. 销毁 Bean (调用自定义 destroy 方法)

5.3.3. Spring Boot 对比:使用注解定义生命周期

通过在 <bean> 标签上设置 init-methoddestroy-method 属性来指定回调方法,并通过定义 <bean> 来注册后置处理器。

1
2
3
4
5
6
<bean id="lifecycleBean"
class="com.example.spring6.bean.LifecycleBean"
init-method="init"
destroy-method="destroy"/>

<bean class="com.example.spring6.bean.LogBeanPostProcessor"/>

对比总结: @PostConstruct@PreDestroyJSR-250 标准的一部分,这意味着您的代码不直接依赖于 Spring 的特定 API,具有更好的可移植性和通用性。这是在现代 Spring Boot 应用中处理生命周期回调的首选方式