likes
comments
collection
share

Spring Bean深度解析:深入理解对象实例的生命周期与应用

作者站长头像
站长
· 阅读数 19

什么是Spring Bean

Spring Bean 是 Spring 框架中的一个核心概念。它是一个由 Spring 容器管理的对象实例。

创建一个 Spring Bean 的基本步骤如下:

  • 定义一个 Java 类,该类将被用作 Spring Bean。这个类可以是普通的 Java 类,无需实现任何特定接口或继承特定类。
  • 在 Spring 配置中声明这个 Java 类作为一个 Bean。可以使用 XML 配置文件、Java 注解或者 Java 代码来完成这个步骤。
  • 在需要使用这个 Bean 的地方,将其注入到其他 Bean 中。可以使用自动装配(autowiring)或者显式配置来完成这个步骤。

下面是一个简单的 Spring Bean 示例,使用注解进行配置:

import org.springframework.stereotype.Component;

@Component
public class MyService {
    public String getMessage() {
        return "Hello, Spring Bean!";
    }
}

在这个示例中,我们定义了一个名为 MyService 的 Java 类,并使用 @Component 注解将其声明为一个 Spring Bean。现在,Spring 容器将负责创建和管理 MyService 的实例。在其他需要使用 MyService 的地方,我们可以使用依赖注入将其注入到相应的 Bean 中。例如:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MyController {
    private final MyService myService;

    @Autowired
    public MyController(MyService myService) {
        this.myService = myService;
    }

    public void printMessage() {
        System.out.println(myService.getMessage());
    }
}

在这个示例中,我们创建了一个名为 MyController 的 Java 类,并使用 @Component 注解将其声明为一个 Spring Bean。使用 @Autowired 注解,我们将 MyService 注入到 MyController 的构造函数中。这样,MyController 可以使用 MyService 提供的功能。

Bean生命周期

Spring Bean深度解析:深入理解对象实例的生命周期与应用

Spring Bean 的生命周期是指从创建到销毁的整个过程。Spring 容器负责管理 Bean 的生命周期,从而确保正确地初始化、配置和销毁对象。下面是 Spring Bean 生命周期的主要阶段:

  1. 实例化:Spring 容器首先创建 Bean 的实例。这是通过调用 Bean 的无参构造函数(或有参构造函数,如果使用构造器注入)来完成的。
  2. 属性设置:在实例化后,Spring 容器为 Bean 设置属性值。这些属性值可以通过 XML 配置文件、Java 注解或 Java 代码来定义。在这个阶段,依赖注入(Dependency Injection)也会发生,将其他 Bean 注入到当前 Bean 中。
  3. 调用 Bean 的初始化方法
    • 如果 Bean 实现了 org.springframework.beans.factory.InitializingBean 接口,Spring 容器会在设置完属性后调用其 afterPropertiesSet() 方法。
    • 如果在 Bean 配置中指定了自定义初始化方法(例如,通过 @Bean(initMethod = "customInit") 注解或 XML 配置文件的 init-method 属性),Spring 容器会在设置完属性后调用该方法。
  4. Post-processing:在 Bean 初始化完成后,Spring 容器可以执行一些后处理操作。这是通过实现 org.springframework.beans.factory.config.BeanPostProcessor 接口完成的。例如,ApplicationContextAwareProcessor 是一个内置的 BeanPostProcessor,它负责处理实现了 ApplicationContextAware 接口的 Bean。
  5. Bean 准备使用:此时,Bean 已经被正确地初始化和配置,可以在应用程序中使用了。
  6. 调用 Bean 的销毁方法
    • 如果 Bean 实现了 org.springframework.beans.factory.DisposableBean 接口,Spring 容器会在销毁 Bean 之前调用其 destroy() 方法。
    • 如果在 Bean 配置中指定了自定义销毁方法(例如,通过 @Bean(destroyMethod = "customDestroy") 注解或 XML 配置文件的 destroy-method 属性),Spring 容器会在销毁 Bean 之前调用该方法。
  7. 销毁:当应用程序关闭或 Spring 容器不再需要 Bean 时,容器将销毁 Bean 实例。这可以通过调用 ConfigurableApplicationContext.close() 方法或者销毁 Spring 容器来触发。

需要注意的是,这些生命周期阶段可能因 Bean 的具体配置和使用的 Spring 功能而略有不同。

Bean的初始化

@PostConstruct(提倡)

Spring 非常提倡的一种方式,我们通常将其标记在方法上即可,通常习惯将这个方法起名为 init()

@PostConstruct
public void init() {
	System.out.println("Inside init() method...");
}

实现InitializingBean接口

我们可以通过实现 InitializingBean 接口,在其唯一方法 afterPropertiesSet 内完成实例化的工作,官方并不建议我们通过这种方法来完成 Bean 的实例化,这是一种强耦合的方式,一般框架层面才会用到这个方法。

public interface InitializingBean {
		void afterPropertiesSet() throws Exception;
}

init-method属性

<bean id="myClass" class="com.demo.MyClass" init-method="init"/>

或者

public class MyService {
    private String message;

    public void setMessage(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void customInit() {
        System.out.println("执行自定义初始化方法");
        if (message == null) {
            message = "默认消息";
        }
    }
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean(initMethod = "customInit")
    public MyService myService() {
        MyService myService = new MyService();
        myService.setMessage("Hello, Spring!");
        return myService;
    }
}

实现BeanPostProcessor

  • 这个接口的方法是会对所有Bean生效的
  • 使用 postProcessAfterInitialization 方法,通过读取 Bean 的注解完成一些后续逻辑编写与属性的设定
public interface BeanPostProcessor {

    Object postProcessBeforeInitialization(Object var1, String var2) throws BeansException;

    Object postProcessAfterInitialization(Object var1, String var2) throws BeansException;

}

初始化方法调用顺序

  • BeanPostProcessorpostProcessBeforeInitialization
  • @PostConstruct
  • InitializingBean.afterPropertiesSet()
  • 其次调用通过 XML 配置的 init-method 方法或通过设置 @Bean 注解 设置 initMethod 属性的方法
  • BeanPostProcessorpostProcessAfterInitialization

Bean的销毁

@PreDestroy

@PreDestroy
public void destroy1() {
    System.out.println("【" + this.getClass().getSimpleName() + "】注解@PreDestroy定义的自定义销毁方法");
}

实现DisposableBean接口

public class demo implements DisposableBean {
    public void destroy() throws Exception {
        System.out.println("【" + this.getClass().getSimpleName() + "】 DisposableBean method");
    }
}

destroy-method属性

<bean id="myClass" class="com.demo.MyClass" destroy-method="init"/>

或者

public class MyService {
    private String message;

    public void setMessage(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void customDestroy() {
        System.out.println("执行自定义销毁方法");
    }
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean(destroyMethod = "customDestroy")
    public MyService myService() {
        MyService myService = new MyService();
        myService.setMessage("Hello, Spring!");
        return myService;
    }
}

销毁方式调用顺序

  • @PreDestroy
  • DisposableBean.destroy()
  • destroy-method属性

Bean作用域

Spring Bean 的作用域决定了 Bean 实例的生命周期和可见性。Spring 提供了以下几种常用的作用域:

  1. Singleton(单例):这是默认的作用域。在这种作用域下,Spring 容器为每个 Bean ID 创建且仅创建一个 Bean 实例。所有对该 Bean 的请求都共享相同的实例。单例 Bean 的生命周期与 Spring 容器的生命周期相同。
  2. Prototype(原型):在这种作用域下,每次请求 Bean 时,Spring 容器都会创建一个新的 Bean 实例。这意味着每个 Bean 实例都是独立的,具有不同的状态。原型 Bean 的生命周期由创建它的客户端(请求者)决定,而不是由 Spring 容器管理。
  3. Request(请求):这是一个仅在 Web 应用程序上下文中可用的作用域。在请求作用域下,每次 HTTP 请求都会创建一个新的 Bean 实例。这意味着在同一个 HTTP 请求内,所有对该 Bean 的引用都共享相同的实例。请求作用域的 Bean 的生命周期与 HTTP 请求的生命周期相同。
  4. Session(会话):这是一个仅在 Web 应用程序上下文中可用的作用域。在会话作用域下,每个 HTTP 会话都会创建一个新的 Bean 实例。这意味着在同一个 HTTP 会话内,所有对该 Bean 的引用都共享相同的实例。会话作用域的 Bean 的生命周期与 HTTP 会话的生命周期相同。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

@Configuration
public class AppConfig {

    @Bean
    @Scope("prototype")
    public MyService myService() {
        return new MyService();
    }
}

Bean注入方式

在 Spring 中,有三种注入方式可以将依赖注入到 Bean 中:Setter 注入,构造器注入和字段注入

构造器注入(Constructor injection)

构造器注入是通过调用 Bean 的构造器将依赖注入到 Bean 中。这种方式要求 Bean 类具有相应的构造器。

public class MyService {
    private final AnotherService anotherService;

    @Autowired
    public MyService(AnotherService anotherService) {
        this.anotherService = anotherService;
    }
}

字段注入(Field Injection)

字段注入是直接在 Bean 类的字段上使用 @Autowired 注解进行依赖注入。这种方式不需要显式的 setter 方法或构造器。

public class MyService {
    @Autowired
    private AnotherService anotherService;
}

Setter 注入(Setter Injection)

调用 Bean 的 setter 方法将依赖注入到 Bean 中。要求 Bean 类具有相应的 setter 方法。

public class MyService {
    private AnotherService anotherService;

    @Autowired
    public void setAnotherService(AnotherService anotherService) {
        this.anotherService = anotherService;
    }
}

@Resource vs @Autowired

@Resource@Autowired 都是 Spring 框架提供的注解,用于实现依赖注入。虽然它们的功能非常相似,但它们之间还是存在一些差异:

  1. 来源@Autowired 注解是 Spring 框架自带的,而 @Resource 注解则来自 Java 标准的 javax.annotation 包。@Resource 是 JSR-250 规范中定义的,因此它不仅适用于 Spring 框架,还可以在其他支持该规范的框架中使用。
  2. 注入方式@Autowired 默认按类型(by-type)进行依赖注入,也可以通过指定 @Qualifier 注解来按名称(by-name)进行注入。@Resource 注解默认按名称(by-name)进行依赖注入,如果没有找到匹配的 Bean 名称,它会尝试按类型(by-type)进行注入。可以通过设置 @Resource 注解的 name 属性来指定需要注入的 Bean 的名称。
  3. 可选依赖@Autowired 注解默认情况下要求依赖必须存在,如果找不到匹配的 Bean,Spring 容器会抛出异常。要使依赖变为可选的,可以将 @Autowired 注解的 required 属性设置为 false@Resource 注解默认情况下也要求依赖必须存在,但没有提供类似于 @Autowiredrequired 属性。要实现可选依赖,可以使用 @Autowired 注解并设置 required 属性为 false

举例说明:

使用 @Autowired 注解按类型注入:

@Autowired
private MyService myService;

使用 @Autowired 注解并结合 @Qualifier 按名称注入:

@Autowired
@Qualifier("myService")
private MyService myService;

使用 @Resource 注解按名称注入:

@Resource(name = "myService")
private MyService myService;

总的来说,@Resource@Autowired 都可以实现依赖注入,选择哪一个取决于具体需求和场景。在 Spring 项目中,通常更推荐使用 @Autowired,因为它是 Spring 框架的一部分并且提供了更灵活的配置选项。然而,如果需要确保代码与其他支持 JSR-250 规范的框架兼容,可以考虑使用 @Resource 注解。

条件装配Bean

在 Spring Boot 中,可以使用 @ConditionalOn 系列注解进行条件装配。条件装配允许你根据特定的条件(例如,配置属性、环境变量或类的存在)来决定是否创建并注册 Bean。以下是一些常用的条件装配注解:

  1. @ConditionalOnBean:当容器中存在指定类型的 Bean 时,才创建并注册当前 Bean。
  2. @ConditionalOnMissingBean:当容器中不存在指定类型的 Bean 时,才创建并注册当前 Bean。
  3. @ConditionalOnClass:当类路径中存在指定的类时,才创建并注册当前 Bean。
  4. @ConditionalOnMissingClass:当类路径中不存在指定的类时,才创建并注册当前 Bean。
  5. @ConditionalOnProperty:当指定的配置属性具有特定的值时,才创建并注册当前 Bean。
  6. @ConditionalOnResource:当类路径中存在指定的资源文件时,才创建并注册当前 Bean。
  7. @ConditionalOnExpression:当 SpEL 表达式计算为 true 时,才创建并注册当前 Bean。
  8. @ConditionalOnWebApplication:当当前应用是一个 Web 应用时,才创建并注册当前 Bean。
  9. @ConditionalOnNotWebApplication:当当前应用不是一个 Web 应用时,才创建并注册当前 Bean。

@ConditionalOnClass 和 @ConditionalOnProperty

@Configuration
public class MyConfiguration {
    @Bean
    @ConditionalOnClass(name = "com.example.SomeClass")
    @ConditionalOnProperty(name = "my.feature.enabled", havingValue = "true", matchIfMissing = true)
    public MyService myService() {
        return new MyService();
    }
}

在这个示例中,MyService Bean 仅在类路径中存在 com.example.SomeClass 类并且配置属性 my.feature.enabled 的值为 true 时被创建和注册。如果配置属性不存在,matchIfMissing = true 会使条件为 true。

@Conditional

还可以创建自定义条件注解,通过实现 Condition 接口并使用 @Conditional 注解。这可以让你更灵活地定义条件装配逻辑

首先,创建 OperatingSystemCondition 类并实现 Condition 接口:

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class OperatingSystemCondition implements Condition {

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String osName = System.getProperty("os.name");
        return osName != null && osName.toLowerCase().contains("windows");
    }
}

接下来,在配置类中使用 @Conditional 注解并指定 OperatingSystemCondition

@Configuration
public class OperatingSystemConfiguration {

    @Bean
    @Conditional(OperatingSystemCondition.class)
    public WindowsService windowsService() {
        return new WindowsService();
    }
}

在这个示例中,WindowsService Bean 仅在操作系统为 Windows 时被创建和注册。通过实现 Condition 接口并使用 @Conditional 注解,你可以根据自定义的条件来装配 Bean。

Spring Bean 循环依赖

循环依赖(Circular Dependency)是指在一个系统中,两个或者多个组件之间相互依赖的情况。在这种情况下,组件 A 依赖于组件 B,而组件 B 同时也依赖于组件 A。这种相互依赖的关系会导致在实例化对象时出现问题,因为每个组件都需要对方先被实例化。从拓扑图上看,会形成一个环.

Spring是通过提前暴露半成品bean来解决循环依赖的:

  1. 当Spring初始化一个bean时,它先创建bean的实例,然后再逐一设置它的属性。
  2. 如果此时发现需要注入的属性引用的是另一个bean,而这个bean还没有创建,Spring会将当前bean作为"半成品"暴露出去。
  3. 当依赖的bean创建完后,Spring会再来完成"半成品"bean的创建过程,把依赖的bean注入进去。
  4. 这样就可以解决A依赖B,B依赖A的循环依赖问题。

以下是 Spring 解决循环依赖问题的简要过程:

  1. Spring 首先尝试在单例缓存池(Singleton Cache)中查找是否已经创建过该 Bean,如果找到则直接返回。
  2. 如果在单例缓存池中未找到,Spring 会尝试在早期单例缓存池(Early Singleton Cache)中查找。这个缓存池中存放的是尚未完成属性注入的 Bean 实例。如果找到,则直接返回该 Bean 实例,并完成循环依赖。
  3. 如果在早期单例缓存池中也未找到,Spring 会在单例工厂缓存池(Singleton Factory Cache)中查找。这个缓存池中存放的是 Bean 工厂,可以用于创建 Bean 实例。如果找到,则通过这个工厂创建一个新的 Bean 实例,并放入早期单例缓存池中。
  4. 如果在前三个缓存池中都未找到,Spring 会创建一个新的 Bean 实例,并将其放入早期单例缓存池中。然后开始处理依赖的 Bean,重复上述过程。
  5. 当所有依赖的 Bean 都被处理完毕,Spring 会将当前 Bean 从早期单例缓存池移除,并将其放入单例缓存池中。

对于Spring循环依赖的情况总结如下:

  • 不能解决的情况: 构造器注入循环依赖, prototype作用域:field属性注入循环依赖
  • 能解决的情况: field属性注入(setter方法注入)循环依赖

参考

Spring 是如何解决循环依赖的?

一文告诉你Spring是如何利用"三级缓存"巧妙解决Bean的循环依赖问题的【享学Spring】