Spring概念:Bean 的生命周期
Spring Bean 的生命周期描述了一个 Bean 从创建到销毁的整个过程,理解 Bean 的生命周期有助于我们在不同阶段对 Bean 进行自定义处理,实现特定的业务需求。下面详细介绍 Bean 的生命周期阶段,并给出示例代码。
1. Bean 生命周期的主要阶段
1.1 Bean 定义阶段
- 解释:在 Spring 配置文件(XML 或 Java 配置类)中定义 Bean 的信息,包括 Bean 的类名、作用域、依赖关系等。Spring 容器会根据这些定义来创建和管理 Bean 实例。
- 示例:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
1.2 Bean 实例化阶段
- 解释:Spring 容器根据 Bean 的定义信息,使用反射机制创建 Bean 的实例。这个阶段只是创建了一个空的对象,还没有进行属性注入。
- 示例:在上述配置中,
myBean()方法返回的MyBean对象就是在这个阶段被创建的。
1.3 属性注入阶段
- 解释:Spring 容器将 Bean 所依赖的其他 Bean 或配置属性注入到 Bean 实例中。可以通过构造函数注入、Setter 方法注入或字段注入等方式实现。
- 示例:
public class MyBean {
private AnotherBean anotherBean;
public void setAnotherBean(AnotherBean anotherBean) {
this.anotherBean = anotherBean;
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public AnotherBean anotherBean() {
return new AnotherBean();
}
@Bean
public MyBean myBean() {
MyBean bean = new MyBean();
bean.setAnotherBean(anotherBean());
return bean;
}
}
1.4 BeanPostProcessor 的前置处理阶段
- 解释:在 Bean 实例化和属性注入完成后,Spring 容器会调用实现了
BeanPostProcessor接口的postProcessBeforeInitialization方法,允许开发者在 Bean 初始化方法调用之前对 Bean 进行自定义处理。 - 示例:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Before initialization: " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("After initialization: " + beanName);
return bean;
}
}
1.5 初始化阶段
- 解释:在这个阶段,Spring 容器会调用 Bean 的初始化方法。可以通过实现
InitializingBean接口的afterPropertiesSet方法,或者使用@PostConstruct注解、@Bean注解的initMethod属性来指定初始化方法。 - 示例:
import javax.annotation.PostConstruct;
public class MyBean {
@PostConstruct
public void init() {
System.out.println("MyBean initialized");
}
}
1.6 BeanPostProcessor 的后置处理阶段
- 解释:在 Bean 的初始化方法调用完成后,Spring 容器会调用实现了
BeanPostProcessor接口的postProcessAfterInitialization方法,允许开发者在 Bean 初始化方法调用之后对 Bean 进行自定义处理。 - 示例:见上面
CustomBeanPostProcessor类的postProcessAfterInitialization方法。
1.7 使用阶段
- 解释:Bean 初始化完成后,就可以被应用程序使用了。在这个阶段,Bean 处于可用状态,应用程序可以调用 Bean 的方法来完成具体的业务逻辑。
1.8 销毁阶段
- 解释:当 Spring 容器关闭时,会调用 Bean 的销毁方法。可以通过实现
DisposableBean接口的destroy方法,或者使用@PreDestroy注解、@Bean注解的destroyMethod属性来指定销毁方法。 - 示例:
import javax.annotation.PreDestroy;
public class MyBean {
@PreDestroy
public void destroy() {
System.out.println("MyBean destroyed");
}
}
2. 完整示例代码
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
// Bean 类
class MyBean {
@PostConstruct
public void init() {
System.out.println("MyBean initialized");
}
public void doSomething() {
System.out.println("MyBean is doing something");
}
@PreDestroy
public void destroy() {
System.out.println("MyBean destroyed");
}
}
// BeanPostProcessor 实现类
@Component
class CustomBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Before initialization: " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("After initialization: " + beanName);
return bean;
}
}
// 配置类
@Configuration
class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
// 主类
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyBean myBean = context.getBean(MyBean.class);
myBean.doSomething();
context.close();
}
}
3. 代码执行流程
- Spring 容器启动,加载
AppConfig配置类。 - 根据
@Bean注解创建MyBean实例。 - 进行属性注入(这里没有依赖,所以无实际操作)。
- 调用
CustomBeanPostProcessor的postProcessBeforeInitialization方法。 - 调用
MyBean的init方法进行初始化。 - 调用
CustomBeanPostProcessor的postProcessAfterInitialization方法。 - 调用
MyBean的doSomething方法进行业务处理。 - 关闭 Spring 容器,调用
MyBean的destroy方法进行销毁。
通过以上示例,我们可以清晰地看到 Spring Bean 的完整生命周期。在实际开发中,可以根据需要在不同的生命周期阶段添加自定义逻辑,实现对 Bean 的灵活控制。
为什么 Spring Bean 会产生循环依赖问题?
Spring Bean 产生循环依赖问题主要是由于 Bean 之间的依赖关系形成了闭环,也就是多个 Bean 相互依赖,导致 Spring 容器在创建这些 Bean 时陷入死循环,无法正常完成 Bean 的初始化。下面从不同角度详细分析产生循环依赖的原因:
1. 构造函数注入循环依赖
原因解释
当使用构造函数进行依赖注入时,Spring 容器在创建 Bean 实例时需要先获取其依赖的 Bean。如果存在循环依赖,就会出现 A 依赖 B,B 又依赖 A 的情况,Spring 容器在创建 A 时发现需要 B,去创建 B 时又发现需要 A,从而陷入死循环。
示例代码
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
public class B {
private A a;
public B(A a) {
this.a = a;
}
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class AppConfig {
@Bean
public A a() {
return new A(b());
}
@Bean
public B b() {
return new B(a());
}
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
}
}
在上述代码中,A 的构造函数依赖 B,B 的构造函数依赖 A,Spring 容器在创建 A 时会先去创建 B,而创建 B 又需要 A,这样就形成了循环依赖,导致 Spring 容器无法完成 Bean 的创建。
2. Setter 方法注入循环依赖
原因解释
Setter 方法注入循环依赖相对复杂一些。Spring 容器在创建 Bean 时,会先实例化 Bean,然后再进行属性注入。在属性注入阶段,如果存在循环依赖,可能会导致 Bean 在未完全初始化的情况下就被引用。不过,Spring 对于单例的 Setter 方法注入循环依赖有一定的解决方案,这是因为 Spring 采用了三级缓存机制。
示例代码
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class AppConfig {
@Bean
public A a() {
A a = new A();
a.setB(b());
return a;
}
@Bean
public B b() {
B b = new B();
b.setA(a());
return b;
}
}
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
}
}
在这个例子中,A 通过 setB 方法依赖 B,B 通过 setA 方法依赖 A。虽然 Spring 可以通过三级缓存解决单例的 Setter 方法注入循环依赖,但这种依赖关系仍然是不合理的,可能会导致代码的可维护性变差。
3. 循环依赖的本质原因
循环依赖的本质原因是 Bean 之间的依赖关系设计不合理,违反了软件设计的单一职责原则和依赖倒置原则。在设计 Bean 时,应该尽量避免出现循环依赖,保持 Bean 之间的依赖关系清晰、简单。如果确实需要多个 Bean 之间进行交互,可以考虑引入中间层或者使用事件驱动等方式来解耦。
4. 总结
- 构造函数注入循环依赖会导致 Spring 容器创建 Bean 时陷入死循环,因为构造函数需要在 Bean 实例化时就获取依赖的 Bean。
- Setter 方法注入循环依赖虽然 Spring 对单例情况有一定的解决方案,但仍然会导致代码的可维护性变差,并且可能会出现 Bean 在未完全初始化的情况下被引用的问题。
- 循环依赖的根本原因是 Bean 依赖关系设计不合理,应该尽量避免这种情况的发生。
三大循环依赖问题场景
在 Spring 中,循环依赖通常指的是多个 Bean 之间相互依赖形成一个闭环,导致 Spring 容器在创建这些 Bean 时无法正常完成初始化。以下详细介绍三种常见的循环依赖问题场景。
1. 构造器注入循环依赖
场景描述
当使用构造器进行依赖注入时,若多个 Bean 通过构造器相互依赖,就会产生循环依赖问题。因为 Spring 在创建 Bean 实例时,会调用其构造器,若构造器参数依赖其他 Bean,就会先去创建这些依赖的 Bean。若形成循环依赖,就会陷入死循环,无法完成 Bean 的创建。
示例代码
// Bean A
class A {
private B b;
public A(B b) {
this.b = b;
}
}
// Bean B
class B {
private A a;
public B(A a) {
this.a = a;
}
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 配置类
@Configuration
class AppConfig {
@Bean
public A a() {
return new A(b());
}
@Bean
public B b() {
return new B(a());
}
}
// 主类
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
}
}
问题分析
在上述代码中,A 的构造器依赖 B,B 的构造器依赖 A。Spring 容器创建 A 时,会调用 A 的构造器,而构造器需要 B 的实例,于是去创建 B。创建 B 时,又需要 A 的实例,这样就形成了循环依赖,Spring 无法完成 A 和 B 的创建。
2. Setter 方法注入循环依赖(单例)
场景描述
对于单例 Bean,使用 Setter 方法进行依赖注入时,Spring 有一定的机制来解决循环依赖问题,但这并不意味着这种依赖关系是合理的。在这种场景下,Spring 先创建 Bean 的实例,再通过 Setter 方法注入依赖。
示例代码
// Bean A
class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
// Bean B
class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 配置类
@Configuration
class AppConfig {
@Bean
public A a() {
A a = new A();
a.setB(b());
return a;
}
@Bean
public B b() {
B b = new B();
b.setA(a());
return b;
}
}
// 主类
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
}
}
问题分析
Spring 容器创建 A 时,先实例化 A,然后在注入 B 依赖时发现需要创建 B。创建 B 时,先实例化 B,在注入 A 依赖时,由于 A 已经实例化(虽然未完全初始化),可以将这个半成品的 A 注入给 B。B 完成初始化后,再将其注入给 A,从而解决循环依赖问题。不过,这种依赖关系会使代码的可维护性变差。
3. Setter 方法注入循环依赖(原型)
场景描述
对于原型(prototype)作用域的 Bean,使用 Setter 方法进行依赖注入时,Spring 无法解决循环依赖问题。因为原型 Bean 每次请求都会创建新的实例,Spring 不会缓存原型 Bean 的半成品实例。
示例代码
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
// Bean A
class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
// Bean B
class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
// 配置类
@Configuration
class AppConfig {
@Bean
@Scope("prototype")
public A a() {
A a = new A();
a.setB(b());
return a;
}
@Bean
@Scope("prototype")
public B b() {
B b = new B();
b.setA(a());
return b;
}
}
// 主类
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
A a = context.getBean(A.class);
}
}
问题分析
当请求 A 的实例时,Spring 会创建一个新的 A 实例,在注入 B 依赖时,会创建一个新的 B 实例。创建 B 实例时,又需要创建一个新的 A 实例,如此循环,导致 Spring 无法完成 A 和 B 的创建。
总结
- 构造器注入循环依赖会使 Spring 容器创建 Bean 时陷入死循环,无法解决。
- 单例 Bean 的 Setter 方法注入循环依赖,Spring 可通过三级缓存机制解决,但会影响代码可维护性。
- 原型 Bean 的 Setter 方法注入循环依赖,Spring 无法解决。在设计 Bean 依赖关系时,应尽量避免循环依赖的出现。

浙公网安备 33010602011771号