Spring Bean生命周期:从诞生到消亡的那些事儿
两年前做电商项目时,我曾栽过一个特别"隐蔽"的坑:订单服务里的支付回调处理器,偶尔会出现数据库连接为空的情况。排查了三天,日志翻了无数遍,最后才发现是Bean的初始化顺序出了问题——数据源Bean还没创建完成,支付回调处理器的初始化方法就已经执行了,导致依赖注入失败。那是我第一次真切感受到:不搞懂Spring Bean的生命周期,写Spring项目就像在"盲人摸象"。
很多程序员刚学Spring时,都觉得"把类加个@Service或@Component注解,Spring就会自动管理Bean",这其实只看到了表面。Bean从被Spring"发现",到初始化、完成依赖注入,再到最终销毁,整个过程藏着一套严密的流程。今天就结合我的开发经历,把Bean生命周期掰开揉碎了讲,再聊聊那些年踩过的生命周期相关的坑。
一、先搞懂核心:Bean生命周期的"十二步心法"
不少资料把Bean生命周期讲得太复杂,动辄十几二十步,反而让人抓不住重点。其实站在开发视角,核心流程就十二步,我画了个简化版的流程图,先有个整体认知:
这十二步里,有几个关键节点是开发中最常接触的,也是最容易出问题的。下面结合我做过的用户服务案例,逐个拆解核心步骤。
关键节点1:实例化与属性设置——Bean的"诞生第一步"
实例化(第一步)和属性设置(第二步)是Bean生命周期的起点,对应我们写Java代码时"new对象"和"给对象的属性赋值"两个操作。但Spring的实例化有个特殊点:它是通过反射实现的,而且实例化时只分配内存,此时Bean的属性还是默认值(比如String为null,int为0)。
我之前在用户服务里定义过一个UserService,代码大概是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class UserService {
@Autowired
private UserDao userDao;
private String serviceName;
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
}
Spring首先会通过反射调用UserService的无参构造器,创建一个UserService实例(实例化),此时userDao是null,serviceName也是null。然后Spring会通过setter方法把配置文件里的serviceName值传进去,同时把UserDao的实例注入到userDao属性中(属性设置)。这里有个小细节:如果UserService没有无参构造器,且没有显式定义有参构造器,Spring实例化时会直接报错——这个坑我在初学的时候踩过,当时为了传参加了个有参构造器,结果忘了保留无参的,启动项目直接报"No default constructor found"。
关键节点2:初始化阶段——Bean的"成长修炼"
初始化阶段是Bean生命周期的核心,从第三步到第九步都属于这个阶段,主要做三件事:实现Aware接口获取Spring容器信息、通过BeanPostProcessor做增强、执行自定义初始化逻辑。这部分也是开发中最常自定义的地方。
比如我曾在项目中需要获取当前Bean的名称,就实现了BeanNameAware接口:
1
2
3
4
5
6
7
8
9
10
11
@Service
public class UserService implements BeanNameAware {
private String beanName;
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("当前Bean的名称:" + beanName);
}
}
启动项目后,控制台会输出"当前Bean的名称:userService",这就是Spring在第三步自动调用setBeanName方法的效果。同理,如果需要获取Spring容器对象,就实现ApplicationContextAware接口,Spring会在第五步注入ApplicationContext。
而初始化逻辑的执行顺序,我曾做过一次实测:在同一个Bean里同时用@PostConstruct注解、实现InitializingBean接口和配置init-method,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
@Bean(initMethod = "customInit")
public class UserService implements InitializingBean {
@PostConstruct
public void postConstructInit() {
System.out.println("1.@PostConstruct注解的方法执行");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("2.InitializingBean的afterPropertiesSet方法执行");
}
public void customInit() {
System.out.println("3.init-method指定的方法执行");
}
}
启动后控制台的输出顺序和代码里标注的一致,这说明Spring执行初始化逻辑的优先级是:@PostConstruct > InitializingBean > init-method。这个顺序很重要,比如我们需要在初始化时用到注入的属性,就可以把逻辑写在@PostConstruct里,确保属性已经设置完成。
关键节点3:销毁阶段——Bean的"优雅落幕"
当Spring容器关闭时,Bean就会进入销毁阶段,对应第十二步。销毁阶段主要用于释放资源,比如关闭数据库连接、释放文件流等。和初始化类似,销毁逻辑也有三种实现方式:@PreDestroy注解、实现DisposableBean接口和配置destroy-method,执行优先级是@PreDestroy > DisposableBean > destroy-method。
我在之前的文件处理服务里,就用@PreDestroy注解释放过文件流:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class FileService {
private FileInputStream fis;
@PostConstruct
public void initFileStream() throws FileNotFoundException {
fis = new FileInputStream("data.txt");
System.out.println("文件流打开成功");
}
@PreDestroy
public void closeFileStream() throws IOException {
if (fis != null) {
fis.close();
System.out.println("文件流关闭成功");
}
}
}
当容器正常关闭时,会自动执行@PreDestroy注解的方法,确保文件流被正确关闭,避免资源泄露。如果是粗暴关闭容器(比如kill进程),销毁方法可能不会执行,这点在生产环境要特别注意。
二、踩坑复盘:那些栽在生命周期上的坑
讲完了理论,再聊聊我这些年踩过的生命周期相关的坑,每个坑都对应一个真实的生产问题,希望能帮大家避坑。
坑1:Bean初始化顺序混乱,依赖注入失败
这就是我开头提到的订单服务问题:支付回调处理器(PayCallbackService)依赖数据源(DataSource),但PayCallbackService的初始化方法先执行了,导致获取数据源时为空。代码大概是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
return new DruidDataSource(...);
}
}
@Service
public class PayCallbackService {
@Autowired
private DataSource dataSource;
@PostConstruct
public void init() {
Connection conn = dataSource.getConnection();
}
}
排查后发现,Spring默认是按Bean的名称字母顺序初始化的,PayCallbackService的首字母是P,DataSource的首字母是D,按理说DataSource会先初始化。但因为DataSource的初始化耗时较长,偶尔会出现PayCallbackService先完成实例化并执行@PostConstruct方法的情况。
解决方案有两个:一是用@DependsOn注解明确指定依赖关系,强制Spring先初始化DataSource;二是把初始化逻辑里使用数据源的代码,放到实现InitializingBean接口的afterPropertiesSet方法里,虽然执行时机和@PostConstruct差不多,但稳定性更高。我当时用了@DependsOn注解,问题就解决了:
1
2
3
4
5
6
@Service
@DependsOn("dataSource")
public class PayCallbackService {
}
坑2:单例Bean的初始化只执行一次,动态属性不生效
Spring默认的Bean作用域是单例,也就是说Bean只会被实例化和初始化一次。我曾在项目中犯过一个错:在单例的UserService里,把用户的登录状态存到了成员变量里,结果导致后续登录的用户拿到了上一个用户的状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class UserService {
private UserLoginVO loginUser;
public void setLoginUser(UserLoginVO user) {
this.loginUser = user;
}
public UserLoginVO getLoginUser() {
return loginUser;
}
}
这就是因为单例Bean的生命周期和容器一致,成员变量会被所有请求共享。解决方案很简单:如果需要存储线程相关的变量,用ThreadLocal;如果需要每次请求都创建新的Bean,就把作用域改成prototype:
1
2
3
4
5
6
7
@Service
@Scope("prototype")
public class UserService {
private UserLoginVO loginUser;
}
坑3:BeanPostProcessor使用不当,导致所有Bean初始化失败
BeanPostProcessor是Spring的"增强器",能对所有Bean的初始化前后进行处理。但我曾因为在BeanPostProcessor里抛出了异常,导致整个容器的Bean都初始化失败。当时的代码是这样的:
1
2
3
4
5
6
7
8
9
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
UserService userService = (UserService) bean;
return bean;
}
}
因为Spring会对所有Bean执行这个方法,当遇到非UserService类型的Bean时,强转就会抛出ClassCastException,导致容器启动失败。正确的做法是先判断Bean的类型,再进行处理:
1
2
3
4
5
6
7
8
9
10
11
12
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof UserService) {
UserService userService = (UserService) bean;
}
return bean;
}
}
三、总结:理解生命周期的核心价值
看到这里,可能有同学会问:搞这么清楚Bean的生命周期,到底有什么用?其实答案很简单:当你需要自定义Bean的创建逻辑、解决依赖注入问题、排查Bean相关的异常时,生命周期就是你的"解题钥匙"。
比如你想在Bean初始化前统一给属性加解密,就用BeanPostProcessor;想在Bean销毁时释放资源,就用@PreDestroy;想控制Bean的创建顺序,就用@DependsOn。这些实际开发中常用的技巧,都建立在对生命周期的理解之上。
最后给大家一个小建议:如果对某个生命周期节点的执行时机不确定,最好的方式就是写个测试类实测一下,就像我之前测试初始化逻辑顺序那样。亲手验证过的知识,比死记硬背要牢固得多。
Bean的生命周期看似复杂,但只要抓住"实例化-初始化-销毁"这三大阶段,再结合实际开发中的案例和坑点去理解,很快就能掌握。希望这篇带着我个人踩坑经历的分享,能帮大家真正搞懂Bean的生命周期,让你在写Spring项目时更有底气。
(注:文档部分内容可能由 AI 生成)
<h2><a id="Spring_Bean_0"></a>Spring Bean生命周期:从诞生到消亡的那些事儿</h2>
<p>两年前做电商项目时,我曾栽过一个特别"隐蔽"的坑:订单服务里的支付回调处理器,偶尔会出现数据库连接为空的情况。排查了三天,日志翻了无数遍,最后才发现是Bean的初始化顺序出了问题——数据源Bean还没创建完成,支付回调处理器的初始化方法就已经执行了,导致依赖注入失败。那是我第一次真切感受到:不搞懂Spring Bean的生命周期,写Spring项目就像在"盲人摸象"。</p>
<p>很多程序员刚学Spring时,都觉得"把类加个@Service或@Component注解,Spring就会自动管理Bean",这其实只看到了表面。Bean从被Spring"发现",到初始化、完成依赖注入,再到最终销毁,整个过程藏着一套严密的流程。今天就结合我的开发经历,把Bean生命周期掰开揉碎了讲,再聊聊那些年踩过的生命周期相关的坑。</p>
<h2><a id="Bean_6"></a>一、先搞懂核心:Bean生命周期的"十二步心法"</h2>
<p>不少资料把Bean生命周期讲得太复杂,动辄十几二十步,反而让人抓不住重点。其实站在开发视角,核心流程就十二步,我画了个简化版的流程图,先有个整体认知:</p>
<div id="mermaid-container-e58b4d769b3b4441b6ab3887265ea58f"></div><p>这十二步里,有几个关键节点是开发中最常接触的,也是最容易出问题的。下面结合我做过的用户服务案例,逐个拆解核心步骤。</p>
<h3><a id="1Bean_27"></a>关键节点1:实例化与属性设置——Bean的"诞生第一步"</h3>
<p>实例化(第一步)和属性设置(第二步)是Bean生命周期的起点,对应我们写Java代码时"new对象"和"给对象的属性赋值"两个操作。但Spring的实例化有个特殊点:它是通过反射实现的,而且实例化时只分配内存,此时Bean的属性还是默认值(比如String为null,int为0)。</p>
<p>我之前在用户服务里定义过一个UserService,代码大概是这样的:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> </span>{
<span class="hljs-comment">// 依赖的Dao层Bean</span>
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> UserDao userDao;
<span class="hljs-comment">// 自定义属性</span>
<span class="hljs-keyword">private</span> String serviceName;
<span class="hljs-comment">// setter方法,用于Spring设置属性</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setServiceName</span><span class="hljs-params">(String serviceName)</span> </span>{
<span class="hljs-keyword">this</span>.serviceName = serviceName;
}
}
</code></div></pre>
<p>Spring首先会通过反射调用UserService的无参构造器,创建一个UserService实例(实例化),此时userDao是null,serviceName也是null。然后Spring会通过setter方法把配置文件里的serviceName值传进去,同时把UserDao的实例注入到userDao属性中(属性设置)。这里有个小细节:如果UserService没有无参构造器,且没有显式定义有参构造器,Spring实例化时会直接报错——这个坑我在初学的时候踩过,当时为了传参加了个有参构造器,结果忘了保留无参的,启动项目直接报"No default constructor found"。</p>
<h3><a id="2Bean_51"></a>关键节点2:初始化阶段——Bean的"成长修炼"</h3>
<p>初始化阶段是Bean生命周期的核心,从第三步到第九步都属于这个阶段,主要做三件事:实现Aware接口获取Spring容器信息、通过BeanPostProcessor做增强、执行自定义初始化逻辑。这部分也是开发中最常自定义的地方。</p>
<p>比如我曾在项目中需要获取当前Bean的名称,就实现了BeanNameAware接口:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">BeanNameAware</span> </span>{
<span class="hljs-keyword">private</span> String beanName;
<span class="hljs-comment">// 实现BeanNameAware接口的方法,Spring会自动传入Bean的名称</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setBeanName</span><span class="hljs-params">(String name)</span> </span>{
<span class="hljs-keyword">this</span>.beanName = name;
System.out.println(<span class="hljs-string">"当前Bean的名称:"</span> + beanName);
}
}
</code></div></pre>
<p>启动项目后,控制台会输出"当前Bean的名称:userService",这就是Spring在第三步自动调用setBeanName方法的效果。同理,如果需要获取Spring容器对象,就实现ApplicationContextAware接口,Spring会在第五步注入ApplicationContext。</p>
<p>而初始化逻辑的执行顺序,我曾做过一次实测:在同一个Bean里同时用@PostConstruct注解、实现InitializingBean接口和配置init-method,代码如下:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-comment">// 配置init-method</span>
<span class="hljs-meta">@Bean</span>(initMethod = <span class="hljs-string">"customInit"</span>)
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">InitializingBean</span> </span>{
<span class="hljs-comment">// @PostConstruct注解的方法</span>
<span class="hljs-meta">@PostConstruct</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">postConstructInit</span><span class="hljs-params">()</span> </span>{
System.out.println(<span class="hljs-string">"1.@PostConstruct注解的方法执行"</span>);
}
<span class="hljs-comment">// 实现InitializingBean接口的方法</span>
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">afterPropertiesSet</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> Exception </span>{
System.out.println(<span class="hljs-string">"2.InitializingBean的afterPropertiesSet方法执行"</span>);
}
<span class="hljs-comment">// 自定义的init-method方法</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">customInit</span><span class="hljs-params">()</span> </span>{
System.out.println(<span class="hljs-string">"3.init-method指定的方法执行"</span>);
}
}
</code></div></pre>
<p>启动后控制台的输出顺序和代码里标注的一致,这说明Spring执行初始化逻辑的优先级是:@PostConstruct > InitializingBean > init-method。这个顺序很重要,比如我们需要在初始化时用到注入的属性,就可以把逻辑写在@PostConstruct里,确保属性已经设置完成。</p>
<h3><a id="3Bean_101"></a>关键节点3:销毁阶段——Bean的"优雅落幕"</h3>
<p>当Spring容器关闭时,Bean就会进入销毁阶段,对应第十二步。销毁阶段主要用于释放资源,比如关闭数据库连接、释放文件流等。和初始化类似,销毁逻辑也有三种实现方式:@PreDestroy注解、实现DisposableBean接口和配置destroy-method,执行优先级是@PreDestroy > DisposableBean > destroy-method。</p>
<p>我在之前的文件处理服务里,就用@PreDestroy注解释放过文件流:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FileService</span> </span>{
<span class="hljs-keyword">private</span> FileInputStream fis;
<span class="hljs-comment">// 初始化时打开文件流</span>
<span class="hljs-meta">@PostConstruct</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initFileStream</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> FileNotFoundException </span>{
fis = <span class="hljs-keyword">new</span> FileInputStream(<span class="hljs-string">"data.txt"</span>);
System.out.println(<span class="hljs-string">"文件流打开成功"</span>);
}
<span class="hljs-comment">// 销毁时关闭文件流</span>
<span class="hljs-meta">@PreDestroy</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">closeFileStream</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException </span>{
<span class="hljs-keyword">if</span> (fis != <span class="hljs-keyword">null</span>) {
fis.close();
System.out.println(<span class="hljs-string">"文件流关闭成功"</span>);
}
}
}
</code></div></pre>
<p>当容器正常关闭时,会自动执行@PreDestroy注解的方法,确保文件流被正确关闭,避免资源泄露。如果是粗暴关闭容器(比如kill进程),销毁方法可能不会执行,这点在生产环境要特别注意。</p>
<h2><a id="_132"></a>二、踩坑复盘:那些栽在生命周期上的坑</h2>
<p>讲完了理论,再聊聊我这些年踩过的生命周期相关的坑,每个坑都对应一个真实的生产问题,希望能帮大家避坑。</p>
<h3><a id="1Bean_136"></a>坑1:Bean初始化顺序混乱,依赖注入失败</h3>
<p>这就是我开头提到的订单服务问题:支付回调处理器(PayCallbackService)依赖数据源(DataSource),但PayCallbackService的初始化方法先执行了,导致获取数据源时为空。代码大概是这样的:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-comment">// 数据源Bean,通过@Configuration配置</span>
<span class="hljs-meta">@Configuration</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DataSourceConfig</span> </span>{
<span class="hljs-meta">@Bean</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> DataSource <span class="hljs-title">dataSource</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">// 初始化数据源,耗时较长</span>
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> DruidDataSource(...);
}
}
<span class="hljs-comment">// 支付回调处理器</span>
<span class="hljs-meta">@Service</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PayCallbackService</span> </span>{
<span class="hljs-meta">@Autowired</span>
<span class="hljs-keyword">private</span> DataSource dataSource;
<span class="hljs-meta">@PostConstruct</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">()</span> </span>{
<span class="hljs-comment">// 初始化时使用数据源,偶尔会报空指针</span>
Connection conn = dataSource.getConnection();
}
}
</code></div></pre>
<p>排查后发现,Spring默认是按Bean的名称字母顺序初始化的,PayCallbackService的首字母是P,DataSource的首字母是D,按理说DataSource会先初始化。但因为DataSource的初始化耗时较长,偶尔会出现PayCallbackService先完成实例化并执行@PostConstruct方法的情况。</p>
<p>解决方案有两个:一是用@DependsOn注解明确指定依赖关系,强制Spring先初始化DataSource;二是把初始化逻辑里使用数据源的代码,放到实现InitializingBean接口的afterPropertiesSet方法里,虽然执行时机和@PostConstruct差不多,但稳定性更高。我当时用了@DependsOn注解,问题就解决了:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-comment">// 明确指定依赖dataSource Bean</span>
<span class="hljs-meta">@Service</span>
<span class="hljs-meta">@DependsOn</span>(<span class="hljs-string">"dataSource"</span>)
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PayCallbackService</span> </span>{
<span class="hljs-comment">// 省略其他代码...</span>
}
</code></div></pre>
<h3><a id="2Bean_178"></a>坑2:单例Bean的初始化只执行一次,动态属性不生效</h3>
<p>Spring默认的Bean作用域是单例,也就是说Bean只会被实例化和初始化一次。我曾在项目中犯过一个错:在单例的UserService里,把用户的登录状态存到了成员变量里,结果导致后续登录的用户拿到了上一个用户的状态。</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-comment">// 单例Bean(默认)</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> </span>{
<span class="hljs-comment">// 错误:把用户状态存到单例Bean的成员变量里</span>
<span class="hljs-keyword">private</span> UserLoginVO loginUser;
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setLoginUser</span><span class="hljs-params">(UserLoginVO user)</span> </span>{
<span class="hljs-keyword">this</span>.loginUser = user;
}
<span class="hljs-function"><span class="hljs-keyword">public</span> UserLoginVO <span class="hljs-title">getLoginUser</span><span class="hljs-params">()</span> </span>{
<span class="hljs-keyword">return</span> loginUser;
}
}
</code></div></pre>
<p>这就是因为单例Bean的生命周期和容器一致,成员变量会被所有请求共享。解决方案很简单:如果需要存储线程相关的变量,用ThreadLocal;如果需要每次请求都创建新的Bean,就把作用域改成prototype:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-meta">@Service</span>
<span class="hljs-comment">// 原型作用域,每次请求都会创建新的Bean</span>
<span class="hljs-meta">@Scope</span>(<span class="hljs-string">"prototype"</span>)
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserService</span> </span>{
<span class="hljs-keyword">private</span> UserLoginVO loginUser;
<span class="hljs-comment">// 省略其他代码...</span>
}
</code></div></pre>
<h3><a id="3BeanPostProcessorBean_211"></a>坑3:BeanPostProcessor使用不当,导致所有Bean初始化失败</h3>
<p>BeanPostProcessor是Spring的"增强器",能对所有Bean的初始化前后进行处理。但我曾因为在BeanPostProcessor里抛出了异常,导致整个容器的Bean都初始化失败。当时的代码是这样的:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyBeanPostProcessor</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">BeanPostProcessor</span> </span>{
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">postProcessBeforeInitialization</span><span class="hljs-params">(Object bean, String beanName)</span> <span class="hljs-keyword">throws</span> BeansException </span>{
<span class="hljs-comment">// 错误:没有判断Bean类型,直接强转</span>
UserService userService = (UserService) bean;
<span class="hljs-keyword">return</span> bean;
}
}
</code></div></pre>
<p>因为Spring会对所有Bean执行这个方法,当遇到非UserService类型的Bean时,强转就会抛出ClassCastException,导致容器启动失败。正确的做法是先判断Bean的类型,再进行处理:</p>
<pre><div class="hljs"><code class="lang-java"><span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyBeanPostProcessor</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">BeanPostProcessor</span> </span>{
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">postProcessBeforeInitialization</span><span class="hljs-params">(Object bean, String beanName)</span> <span class="hljs-keyword">throws</span> BeansException </span>{
<span class="hljs-comment">// 先判断Bean类型</span>
<span class="hljs-keyword">if</span> (bean <span class="hljs-keyword">instanceof</span> UserService) {
UserService userService = (UserService) bean;
<span class="hljs-comment">// 执行增强逻辑</span>
}
<span class="hljs-keyword">return</span> bean;
}
}
</code></div></pre>
<h2><a id="_244"></a>三、总结:理解生命周期的核心价值</h2>
<p>看到这里,可能有同学会问:搞这么清楚Bean的生命周期,到底有什么用?其实答案很简单:当你需要自定义Bean的创建逻辑、解决依赖注入问题、排查Bean相关的异常时,生命周期就是你的"解题钥匙"。</p>
<p>比如你想在Bean初始化前统一给属性加解密,就用BeanPostProcessor;想在Bean销毁时释放资源,就用@PreDestroy;想控制Bean的创建顺序,就用@DependsOn。这些实际开发中常用的技巧,都建立在对生命周期的理解之上。</p>
<p>最后给大家一个小建议:如果对某个生命周期节点的执行时机不确定,最好的方式就是写个测试类实测一下,就像我之前测试初始化逻辑顺序那样。亲手验证过的知识,比死记硬背要牢固得多。</p>
<p>Bean的生命周期看似复杂,但只要抓住"实例化-初始化-销毁"这三大阶段,再结合实际开发中的案例和坑点去理解,很快就能掌握。希望这篇带着我个人踩坑经历的分享,能帮大家真正搞懂Bean的生命周期,让你在写Spring项目时更有底气。</p>
<blockquote>
<p>(注:文档部分内容可能由 AI 生成)</p>
</blockquote>