Spring Bean深度解析:深入理解对象实例的生命周期与应用
什么是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 的实例。这是通过调用 Bean 的无参构造函数(或有参构造函数,如果使用构造器注入)来完成的。
- 属性设置:在实例化后,Spring 容器为 Bean 设置属性值。这些属性值可以通过 XML 配置文件、Java 注解或 Java 代码来定义。在这个阶段,依赖注入(Dependency Injection)也会发生,将其他 Bean 注入到当前 Bean 中。
- 调用 Bean 的初始化方法:
- 如果 Bean 实现了
org.springframework.beans.factory.InitializingBean
接口,Spring 容器会在设置完属性后调用其afterPropertiesSet()
方法。 - 如果在 Bean 配置中指定了自定义初始化方法(例如,通过
@Bean(initMethod = "customInit")
注解或 XML 配置文件的init-method
属性),Spring 容器会在设置完属性后调用该方法。
- 如果 Bean 实现了
- Post-processing:在 Bean 初始化完成后,Spring 容器可以执行一些后处理操作。这是通过实现
org.springframework.beans.factory.config.BeanPostProcessor
接口完成的。例如,ApplicationContextAwareProcessor
是一个内置的BeanPostProcessor
,它负责处理实现了ApplicationContextAware
接口的 Bean。 - Bean 准备使用:此时,Bean 已经被正确地初始化和配置,可以在应用程序中使用了。
- 调用 Bean 的销毁方法:
- 如果 Bean 实现了
org.springframework.beans.factory.DisposableBean
接口,Spring 容器会在销毁 Bean 之前调用其destroy()
方法。 - 如果在 Bean 配置中指定了自定义销毁方法(例如,通过
@Bean(destroyMethod = "customDestroy")
注解或 XML 配置文件的destroy-method
属性),Spring 容器会在销毁 Bean 之前调用该方法。
- 如果 Bean 实现了
- 销毁:当应用程序关闭或 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;
}
初始化方法调用顺序
BeanPostProcessor
的postProcessBeforeInitialization
@PostConstruct
InitializingBean.afterPropertiesSet()
- 其次调用通过 XML 配置的
init-method
方法或通过设置 @Bean 注解 设置initMethod
属性的方法 BeanPostProcessor
的postProcessAfterInitialization
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 提供了以下几种常用的作用域:
- Singleton(单例):这是默认的作用域。在这种作用域下,Spring 容器为每个 Bean ID 创建且仅创建一个 Bean 实例。所有对该 Bean 的请求都共享相同的实例。单例 Bean 的生命周期与 Spring 容器的生命周期相同。
- Prototype(原型):在这种作用域下,每次请求 Bean 时,Spring 容器都会创建一个新的 Bean 实例。这意味着每个 Bean 实例都是独立的,具有不同的状态。原型 Bean 的生命周期由创建它的客户端(请求者)决定,而不是由 Spring 容器管理。
- Request(请求):这是一个仅在 Web 应用程序上下文中可用的作用域。在请求作用域下,每次 HTTP 请求都会创建一个新的 Bean 实例。这意味着在同一个 HTTP 请求内,所有对该 Bean 的引用都共享相同的实例。请求作用域的 Bean 的生命周期与 HTTP 请求的生命周期相同。
- 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 框架提供的注解,用于实现依赖注入。虽然它们的功能非常相似,但它们之间还是存在一些差异:
- 来源:
@Autowired
注解是 Spring 框架自带的,而@Resource
注解则来自 Java 标准的javax.annotation
包。@Resource
是 JSR-250 规范中定义的,因此它不仅适用于 Spring 框架,还可以在其他支持该规范的框架中使用。 - 注入方式:
@Autowired
默认按类型(by-type)进行依赖注入,也可以通过指定@Qualifier
注解来按名称(by-name)进行注入。@Resource
注解默认按名称(by-name)进行依赖注入,如果没有找到匹配的 Bean 名称,它会尝试按类型(by-type)进行注入。可以通过设置@Resource
注解的name
属性来指定需要注入的 Bean 的名称。 - 可选依赖:
@Autowired
注解默认情况下要求依赖必须存在,如果找不到匹配的 Bean,Spring 容器会抛出异常。要使依赖变为可选的,可以将@Autowired
注解的required
属性设置为false
。@Resource
注解默认情况下也要求依赖必须存在,但没有提供类似于@Autowired
的required
属性。要实现可选依赖,可以使用@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。以下是一些常用的条件装配注解:
- @ConditionalOnBean:当容器中存在指定类型的 Bean 时,才创建并注册当前 Bean。
- @ConditionalOnMissingBean:当容器中不存在指定类型的 Bean 时,才创建并注册当前 Bean。
- @ConditionalOnClass:当类路径中存在指定的类时,才创建并注册当前 Bean。
- @ConditionalOnMissingClass:当类路径中不存在指定的类时,才创建并注册当前 Bean。
- @ConditionalOnProperty:当指定的配置属性具有特定的值时,才创建并注册当前 Bean。
- @ConditionalOnResource:当类路径中存在指定的资源文件时,才创建并注册当前 Bean。
- @ConditionalOnExpression:当 SpEL 表达式计算为
true
时,才创建并注册当前 Bean。 - @ConditionalOnWebApplication:当当前应用是一个 Web 应用时,才创建并注册当前 Bean。
- @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
来解决循环依赖的:
- 当Spring初始化一个bean时,它先创建bean的实例,然后再逐一设置它的属性。
- 如果此时发现需要注入的属性引用的是另一个bean,而这个bean还没有创建,Spring会将当前bean作为"半成品"暴露出去。
- 当依赖的bean创建完后,Spring会再来完成"半成品"bean的创建过程,把依赖的bean注入进去。
- 这样就可以解决A依赖B,B依赖A的循环依赖问题。
以下是 Spring 解决循环依赖问题的简要过程:
- Spring 首先尝试在
单例缓存池(Singleton Cache)
中查找是否已经创建过该 Bean,如果找到则直接返回。 - 如果在单例缓存池中未找到,Spring 会尝试在
早期单例缓存池(Early Singleton Cache)
中查找。这个缓存池中存放的是尚未完成属性注入的 Bean 实例。如果找到,则直接返回该 Bean 实例,并完成循环依赖。 - 如果在早期单例缓存池中也未找到,Spring 会在
单例工厂缓存池(Singleton Factory Cache)
中查找。这个缓存池中存放的是 Bean 工厂,可以用于创建 Bean 实例。如果找到,则通过这个工厂创建一个新的 Bean 实例,并放入早期单例缓存池中。 - 如果在前三个缓存池中都未找到,Spring 会创建一个新的 Bean 实例,并将其放入早期单例缓存池中。然后开始处理依赖的 Bean,重复上述过程。
- 当所有依赖的 Bean 都被处理完毕,Spring 会将当前 Bean 从早期单例缓存池移除,并将其放入单例缓存池中。
对于Spring循环依赖的情况总结如下:
- 不能解决的情况: 构造器注入循环依赖, prototype作用域:field属性注入循环依赖
- 能解决的情况: field属性注入(setter方法注入)循环依赖
参考
转载自:https://juejin.cn/post/7244002037192687676