bean的生命周期
在 Spring 中,BeanDefinition
、Bean 实例化、依赖注入、Aware
接口的处理、以及 BeanPostProcessor
的前置和后置处理等,都是 Spring 容器管理 Bean 生命周期的关键部分。下面我将详细解释这些过程。
1. 通过 BeanDefinition
获取 Bean 的定义信息
BeanDefinition
是 Spring 容器中用于描述 Bean 的元数据的接口,包含了 Bean 的配置和信息,如 Bean 的类名、构造参数、属性等。通过 BeanDefinition
,Spring 容器可以知道如何实例化 Bean。
-
获取 BeanDefinition 示例:
Spring 容器在启动时,会加载并解析所有的配置(如 XML 配置文件、注解配置、Java 配置类等),将每个 Bean 的配置存储在BeanDefinition
中。BeanFactory beanFactory = new AnnotationConfigApplicationContext(AppConfig.class); BeanDefinition beanDefinition = beanFactory.getBeanDefinition("myBean"); String beanClassName = beanDefinition.getBeanClassName();
-
BeanDefinition
的常见方法:getBeanClassName()
:获取 Bean 的类名。getPropertyValues()
:获取 Bean 的属性值。getConstructorArgumentValues()
:获取 Bean 的构造方法参数。
2. 调用构造函数实例化 Bean
Spring 通过反射调用构造函数来实例化 Bean。在实例化过程中,Spring 会选择合适的构造器,并根据配置提供的构造参数来实例化对象。
- 步骤:
- Spring 通过
BeanDefinition
中的类名和构造方法配置,选择一个构造方法。 - 如果有构造方法参数(例如在 XML 配置中或注解中配置了构造器注入),Spring 会从配置中获取并注入这些参数。
- 使用反射调用构造函数创建 Bean 实例。
- Spring 通过
例如:
@Bean
public MyBean myBean() {return new MyBean("arg1", 2);
}
3. Bean 的依赖注入
依赖注入是 Spring 中实现控制反转(IoC)的核心机制。Spring 会根据配置将 Bean 的依赖自动注入到 Bean 中。
-
依赖注入的方式:
- 构造器注入:通过 Bean 的构造方法传入依赖。
- Setter 方法注入:通过设置属性的 setter 方法传入依赖。
- 字段注入:通过反射直接注入字段。
-
Spring 如何注入依赖:
- 对于构造器注入,Spring 会根据
@Autowired
注解或 XML 配置来自动匹配依赖的类型。 - 对于字段注入和 setter 注入,Spring 会将依赖通过反射注入到 Bean 中。
- 对于构造器注入,Spring 会根据
4. 处理 Aware 接口(BeanNameAware
、BeanFactoryAware
、ApplicationContextAware
)
Spring 提供了一些接口,让 Bean 在初始化过程中能够访问到容器的相关信息。通过实现这些接口,Bean 可以获得更多的容器信息,如其自身的 Bean 名称、BeanFactory 或 ApplicationContext。
-
BeanNameAware
:通过setBeanName()
方法获取当前 Bean 在容器中的名称。public class MyBean implements BeanNameAware {@Overridepublic void setBeanName(String name) {System.out.println("Bean name is: " + name);} }
-
BeanFactoryAware
:通过setBeanFactory()
方法获得当前 BeanFactory 实例,可以访问容器中的其他 Bean。public class MyBean implements BeanFactoryAware {@Overridepublic void setBeanFactory(BeanFactory beanFactory) {System.out.println("BeanFactory is: " + beanFactory);} }
-
ApplicationContextAware
:通过setApplicationContext()
方法获得当前 ApplicationContext 实例,从而能够访问 Spring 环境和其他 Bean。public class MyBean implements ApplicationContextAware {@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {System.out.println("ApplicationContext is: " + applicationContext);} }
5. Bean 的后置处理器(BeanPostProcessor
)- 前置处理
BeanPostProcessor
是一个接口,允许在 Bean 初始化的前后对 Bean 进行修改。Spring 会自动检测容器中实现了 BeanPostProcessor
接口的类,并在每个 Bean 初始化之前和之后调用相应的方法。
-
postProcessBeforeInitialization
:在 Bean 初始化之前调用,允许你修改 Bean 的属性或执行一些额外的操作。public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("Before initialization: " + beanName);return bean; // 返回修改后的 Bean 对象} }
这里,
postProcessBeforeInitialization
方法在 Bean 的初始化方法(如@PostConstruct
或afterPropertiesSet()
)之前被调用。
6. 初始化方法(InitializingBean
、init-method
)
-
InitializingBean
接口:afterPropertiesSet()
方法会在所有的属性都设置完毕后调用,通常用于进行 Bean 的初始化操作。public class MyBean implements InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("Bean has been initialized");} }
-
@PostConstruct
注解:这个注解标记的方法会在所有的依赖注入完成之后、Bean 初始化之前调用。public class MyBean {@PostConstructpublic void init() {System.out.println("Bean initialized using @PostConstruct");} }
-
自定义初始化方法:在 XML 配置文件中,或者 Java 配置类中,可以通过
init-method
配置一个自定义的初始化方法。<bean id="myBean" class="com.example.MyBean" init-method="init">... </bean>
@Bean(initMethod = "init") public MyBean myBean() {return new MyBean(); }
7. Bean 的后置处理器(BeanPostProcessor
)- 后置处理
BeanPostProcessor
接口还提供了 postProcessAfterInitialization
方法,该方法在 Bean 初始化完成后调用。这个方法可以用于对已经初始化的 Bean 进行修改或增强(如 AOP)。
-
postProcessAfterInitialization
:在 Bean 初始化之后调用,通常用于处理 Bean 的代理或日志功能等。public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("After initialization: " + beanName);return bean; // 返回修改后的 Bean 对象} }
8. 使用(Usage)
-
发生时机:初始化完成后,Spring Bean 就可以进入应用程序的使用阶段。
-
具体操作:
- 这时,Spring Bean 已经是完整的,可以被应用程序中的其他组件或类通过
@Autowired
自动注入、手动查找或通过其它方式使用。 - Bean 可以被正常使用,比如进行方法调用或响应 HTTP 请求等。
- 这时,Spring Bean 已经是完整的,可以被应用程序中的其他组件或类通过
-
关键点:
- 在这个阶段,Bean 已经完全初始化,所有的依赖都已注入,并且初始化方法也已执行。
9. 销毁前处理(Post-process Before Destruction)
-
发生时机:在容器销毁之前,Spring 会进行销毁前的处理。
-
具体操作:
- 如果 Bean 实现了
DisposableBean
接口,Spring 会调用destroy()
方法。 - 如果 Bean 使用了
@PreDestroy
注解,Spring 会在销毁之前调用该方法。 - 如果 Bean 配置了自定义销毁方法(在 XML 配置中通过
destroy-method
或者 Java 配置类中的@Bean(destroyMethod = "...")
),Spring 会调用该方法。
- 如果 Bean 实现了
-
关键点:
- 这个阶段主要是进行资源的释放工作,比如关闭文件流、数据库连接、清理缓存等操作。
10. 销毁(Destruction)
-
发生时机:Spring 容器关闭时,销毁所有的 Bean。
-
具体操作:
- Spring 会在容器销毁之前,销毁所有的 Bean。这个过程包括:
- 释放所有与 Bean 相关的资源。
- 如果 Bean 是
singleton
(单例模式),那么销毁时会调用销毁方法。 - 对于
prototype
(原型模式)Bean,Spring 不负责销毁,通常需要开发者手动销毁。
- Spring 会在容器销毁之前,销毁所有的 Bean。这个过程包括:
-
关键点:
- 容器关闭时,Spring 会销毁所有在容器中管理的 Bean,并释放相应的资源。特别是对于单例 Bean,销毁时会执行注册的销毁方法。
生命周期钩子接口和注解
InitializingBean
和DisposableBean
:这两个接口提供了回调方法(afterPropertiesSet()
和destroy()
)来进行初始化和销毁的自定义操作。@PostConstruct
和@PreDestroy
:Java 注解,分别用于定义初始化和销毁方法。BeanPostProcessor
:可以在 Bean 初始化前后进行处理,常用于 AOP 或修改 Bean 实例。
通过这些不同的阶段和钩子方法,Spring 提供了非常灵活的 Bean 生命周期管理机制,允许开发者定制 Bean 的行为。
总结
Spring 的 Bean 生命周期包括从 Bean 的定义、实例化、依赖注入,到初始化、销毁等多个阶段。在这个过程中,BeanDefinition
提供了 Bean 的配置信息,Spring 会通过反射实例化 Bean,并通过注入不同的依赖来完成 Bean 的构建。BeanPostProcessor
在 Bean 初始化前后提供了扩展点,允许开发者进行自定义的 Bean 修改和增强。同时,通过实现 Aware
接口,Bean 可以获取到容器的一些元数据,从而进一步增强 Bean 的功能。
什么是 Spring 的循环依赖问题?
定义
- 循环依赖指的是两个或多个 Bean 之间互相依赖,形成一个依赖环。例如:
- Bean A 依赖 Bean B,而 Bean B 又依赖 Bean A。
- 更复杂的情况是多个 Bean 相互嵌套依赖,比如 A → B → C → A。
问题出现的场景
- 在 Spring 容器中,Bean 的实例化、初始化是分步骤进行的。如果 Bean 需要其依赖的对象,但该对象还未完成创建,就会导致循环依赖问题。
2. Spring Bean 的创建流程
- 实例化 (Instantiation): 创建 Bean 的原始对象(通过构造器或工厂方法)。
- 属性注入 (Populate Properties): 将依赖的其他 Bean 注入到当前 Bean。
- 初始化 (Initialization): 执行
@PostConstruct
方法、InitializingBean
的回调等初始化逻辑。 - 完成 Bean 创建: Bean 准备好供其他对象使用。
问题出现点:
- 如果两个 Bean 在属性注入阶段互相依赖,Spring 会因依赖未完成而报错。
3. 循环依赖的分类
-
构造器循环依赖:
- 两个或多个 Bean 的构造函数中互相依赖。
- 例如:
@Component public class A {public A(B b) {} }@Component public class B {public B(A a) {} }
- 特点: Spring 无法解决,直接报错,因为在实例化阶段就需要依赖,Spring 没法提前暴露 Bean。
-
字段/Setter 循环依赖:
- 两个或多个 Bean 通过字段或 Setter 方法互相依赖。
- 例如:
@Component public class A {@Autowiredprivate B b; }@Component public class B {@Autowiredprivate A a; }
- 特点: Spring 可以通过 三级缓存 解决。
4. Spring 的解决办法
4.1. 三级缓存机制
Spring 通过三级缓存机制解决 字段/Setter 循环依赖:
-
单例池(一级缓存):
- 保存已经完成初始化的单例 Bean。
singletonObjects
映射:{beanName: beanInstance}
。
-
早期曝光的单例 Bean(二级缓存):
- 保存半成品(实例化但未初始化完成的 Bean)。
earlySingletonObjects
映射:{beanName: earlyBeanInstance}
。
-
单例工厂(三级缓存):
- 保存一个工厂方法,用于生成早期 Bean。
singletonFactories
映射:{beanName: ObjectFactory}
。
4.2. 三级缓存的工作流程
-
实例化阶段:
- 创建 Bean 的实例,但未注入依赖。
- 将工厂方法(
ObjectFactory
)放入三级缓存singletonFactories
中。
-
属性注入阶段:
- 如果 Bean 依赖另一个 Bean,而该 Bean 还未完全初始化:
- 从一级缓存中尝试获取。
- 如果一级缓存未命中,从二级缓存中获取。
- 如果二级缓存仍未命中,从三级缓存中获取,并通过工厂方法生成早期 Bean,随后将其移动到二级缓存。
- 如果 Bean 依赖另一个 Bean,而该 Bean 还未完全初始化:
-
初始化完成后:
- 将完全初始化的 Bean 从二级缓存移动到一级缓存,并从其他缓存中移除。
4.3. 为什么需要三级缓存?
-
问题一:只有一级缓存时
- 只能存放完全初始化完成的 Bean,对于循环依赖无法处理,因为依赖对象未初始化完成时无法注入。
-
问题二:只有二级缓存时
- 二级缓存直接存储早期 Bean 实例,但没有工厂方法来生成代理对象。如果 Bean 是 代理对象(比如使用 AOP),无法提前暴露真实的代理对象。
-
三级缓存的作用
- 在实例化阶段,允许通过工厂方法生成代理对象,从而解决 Bean 的代理问题(如
@Transactional
或@Async
)。 - 工厂方法可以控制生成的是原始对象还是代理对象,再将其放入二级缓存。
- 在实例化阶段,允许通过工厂方法生成代理对象,从而解决 Bean 的代理问题(如
5. 示例代码
循环依赖示例
@Component
public class A {@Autowiredprivate B b;public A() {System.out.println("A is created");}public void doSomething() {System.out.println("A is working with B");}
}@Component
public class B {@Autowiredprivate A a;public B() {System.out.println("B is created");}public void doSomething() {System.out.println("B is working with A");}
}
运行输出
A is created
B is created
A is working with B
B is working with A
解析
- Spring 首先实例化 Bean
A
,但在注入B
时发现B
未完成。 - Spring 实例化 Bean
B
,发现A
尚未完成,将早期的A
暴露到三级缓存中。 - Spring 从三级缓存获取早期的
A
,完成B
的依赖注入。 - 最终完成
A
和B
的初始化。
6. 总结
-
循环依赖类型:
- 构造器循环依赖:无法解决。
- 字段/Setter 循环依赖:通过三级缓存解决。
-
三级缓存的必要性:
- 一级缓存存放完整 Bean。
- 二级缓存存放早期 Bean,但无法处理代理对象问题。
- 三级缓存通过工厂方法解决代理 Bean 的暴露问题。
-
原理优势:
- 三级缓存确保 Spring 能在复杂场景下正确地解析和初始化循环依赖的 Bean,同时支持代理对象和增强功能。
@lazy可以解决构造器循环依赖
1. @Lazy
的作用
当一个 Bean 使用 @Lazy
注解时,Spring 容器并不会在应用启动时立即实例化它,而是等到该 Bean 第一次被使用时才进行实例化。这对于减少应用启动时的初始化时间或者避免不必要的资源消耗非常有帮助。
具体来说,@Lazy
可以用于:
- 懒加载单例 Bean:使某个单例 Bean 在第一次使用时才实例化。
- 懒加载依赖:使 Bean 的依赖对象在实际需要时才被创建,而不是在 Bean 本身被实例化时就创建。
2. @Lazy
的工作原理
@Lazy
的工作原理基于 Spring 的 延迟初始化(lazy initialization)机制。具体来说,它是通过 代理机制 来实现懒加载的。当一个 Bean 被标记为 @Lazy
时,Spring 会为该 Bean 创建一个代理对象,该代理对象会在第一次被访问时才创建实际的 Bean 实例。
3. @Lazy
的实现
- 懒加载的代理:当你将一个 Bean 设置为懒加载时,Spring 会使用代理来实现懒加载。在大多数情况下,Spring 会使用 CGLIB 或 JDK 动态代理 创建代理对象。这个代理对象会在你第一次访问 Bean 时才初始化实际的 Bean 实例,并将其代理对象替换为真正的 Bean 实例。