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. 代码执行流程

  1. Spring 容器启动,加载 AppConfig 配置类。
  2. 根据 @Bean 注解创建 MyBean 实例。
  3. 进行属性注入(这里没有依赖,所以无实际操作)。
  4. 调用 CustomBeanPostProcessorpostProcessBeforeInitialization 方法。
  5. 调用 MyBeaninit 方法进行初始化。
  6. 调用 CustomBeanPostProcessorpostProcessAfterInitialization 方法。
  7. 调用 MyBeandoSomething 方法进行业务处理。
  8. 关闭 Spring 容器,调用 MyBeandestroy 方法进行销毁。

通过以上示例,我们可以清晰地看到 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 的构造函数依赖 BB 的构造函数依赖 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 方法依赖 BB 通过 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 的构造器依赖 BB 的构造器依赖 A。Spring 容器创建 A 时,会调用 A 的构造器,而构造器需要 B 的实例,于是去创建 B。创建 B 时,又需要 A 的实例,这样就形成了循环依赖,Spring 无法完成 AB 的创建。

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 注入给 BB 完成初始化后,再将其注入给 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 无法完成 AB 的创建。

总结

  • 构造器注入循环依赖会使 Spring 容器创建 Bean 时陷入死循环,无法解决。
  • 单例 Bean 的 Setter 方法注入循环依赖,Spring 可通过三级缓存机制解决,但会影响代码可维护性。
  • 原型 Bean 的 Setter 方法注入循环依赖,Spring 无法解决。在设计 Bean 依赖关系时,应尽量避免循环依赖的出现。
posted @ 2025-03-14 18:11  皇问天  阅读(243)  评论(0)    收藏  举报