Spring Bean初始化的三種常用方式(易懂版)
引言
在Spring框架開發(fā)中,Bean的生命周期管理是核心基礎(chǔ)知識(shí)點(diǎn),而Bean初始化作為生命周期中的關(guān)鍵環(huán)節(jié),負(fù)責(zé)在Bean實(shí)例創(chuàng)建并完成依賴注入后,執(zhí)行資源加載、參數(shù)配置、緩存初始化等核心業(yè)務(wù)邏輯。本文將從“是什么-怎么用-為什么”的角度,詳細(xì)拆解Spring中3種主流的Bean初始化方式,每種方式均提供完整可運(yùn)行的代碼示例,并補(bǔ)充執(zhí)行順序驗(yàn)證、場(chǎng)景對(duì)比及實(shí)戰(zhàn)注意事項(xiàng),幫助新手快速掌握并靈活運(yùn)用。
核心前提:Bean初始化的觸發(fā)時(shí)機(jī)——Spring容器創(chuàng)建Bean實(shí)例 → 完成所有屬性的依賴注入(如@Autowired、構(gòu)造器注入) → 執(zhí)行初始化方法(本文重點(diǎn)) → Bean實(shí)例可用。
一、方式1:@PostConstruct注解修飾初始化方法
1.1 核心說明
@PostConstruct是JSR-250規(guī)范(Java EE標(biāo)準(zhǔn))中定義的注解,并非Spring框架專屬,因此具有良好的通用性(可跨框架使用)。該注解僅需修飾在Bean的非靜態(tài)方法上(無參數(shù)、無返回值),當(dāng)Spring容器完成Bean實(shí)例創(chuàng)建和依賴注入后,會(huì)自動(dòng)調(diào)用該方法執(zhí)行初始化邏輯。
依賴要求:JDK9及以上版本中,javax.annotation-api包已被移除,需手動(dòng)引入依賴(Maven示例如下);JDK8及以下版本可直接使用。
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>2.1.1</version>
</dependency>
1.2 完整代碼示例
示例包含4個(gè)核心類:依賴Bean(UserDao)、目標(biāo)Bean(UserService,含@PostConstruct初始化方法)、Spring配置類(注冊(cè)Bean)、測(cè)試類(啟動(dòng)容器驗(yàn)證)。
// 1. 依賴Bean:UserDao(模擬數(shù)據(jù)訪問層,僅作注入示例)
public class UserDao {
// 模擬UserDao的基礎(chǔ)功能
public void initDao() {
System.out.println("UserDao實(shí)例已創(chuàng)建");
}
}
// 2. 目標(biāo)Bean:UserService(核心業(yè)務(wù)類,含@PostConstruct初始化方法)
import javax.annotation.PostConstruct;
public class UserService {
// 依賴注入U(xiǎn)serDao(通過構(gòu)造器注入,Spring會(huì)自動(dòng)匹配)
private final UserDao userDao;
// 構(gòu)造器:Spring容器會(huì)通過該構(gòu)造器注入U(xiǎn)serDao實(shí)例
public UserService(UserDao userDao) {
this.userDao = userDao;
System.out.println("UserService構(gòu)造方法執(zhí)行:已注入U(xiǎn)serDao");
// 注意:此處僅完成屬性注入,Bean尚未初始化,不建議執(zhí)行業(yè)務(wù)邏輯
}
// @PostConstruct修飾的初始化方法:Spring自動(dòng)調(diào)用
@PostConstruct
public void initUserService() {
System.out.println("@PostConstruct初始化方法執(zhí)行:");
System.out.println(" - 加載用戶模塊配置文件");
System.out.println(" - 初始化用戶緩存(如Redis用戶信息預(yù)熱)");
System.out.println(" - 校驗(yàn)用戶服務(wù)依賴資源(如數(shù)據(jù)庫連接)");
// 實(shí)際開發(fā)中可在此處編寫核心初始化邏輯
}
// 模擬業(yè)務(wù)方法(驗(yàn)證Bean可用)
public void getUserInfo() {
System.out.println("執(zhí)行用戶查詢業(yè)務(wù):依賴UserDao完成數(shù)據(jù)獲取");
}
}
// 3. Spring配置類:SpringConfig(注解式配置,替代XML)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // 標(biāo)識(shí)該類為Spring配置類
public class SpringConfig {
// 注冊(cè)UserDao Bean:Spring容器會(huì)管理該實(shí)例
@Bean
public UserDao userDao() {
UserDao userDao = new UserDao();
userDao.initDao(); // 可選:Dao層自身的簡(jiǎn)單初始化
return userDao;
}
// 注冊(cè)UserService Bean:注入U(xiǎn)serDao依賴
@Bean
public UserService userService(UserDao userDao) {
// Spring會(huì)自動(dòng)將上面注冊(cè)的UserDao實(shí)例傳入構(gòu)造器
return new UserService(userDao);
}
}
// 4. 測(cè)試類:BeanInitTest(啟動(dòng)Spring容器,驗(yàn)證初始化流程)
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class BeanInitTest {
public static void main(String[] args) {
// 1. 啟動(dòng)Spring容器(加載注解配置)
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
System.out.println("=== Spring容器啟動(dòng)完成 ===");
// 2. 獲取UserService Bean,驗(yàn)證其可用
UserService userService = context.getBean(UserService.class);
userService.getUserInfo();
// 3. 關(guān)閉容器(釋放資源)
context.close();
}
}
1.3 運(yùn)行結(jié)果與解讀
UserDao實(shí)例已創(chuàng)建 UserService構(gòu)造方法執(zhí)行:已注入U(xiǎn)serDao @PostConstruct初始化方法執(zhí)行: - 加載用戶模塊配置文件 - 初始化用戶緩存(如Redis用戶信息預(yù)熱) - 校驗(yàn)用戶服務(wù)依賴資源(如數(shù)據(jù)庫連接) === Spring容器啟動(dòng)完成 === 執(zhí)行用戶查詢業(yè)務(wù):依賴UserDao完成數(shù)據(jù)獲取
解讀:從結(jié)果可見,執(zhí)行順序?yàn)椤窾serDao實(shí)例創(chuàng)建 → UserService構(gòu)造方法(注入依賴) → @PostConstruct初始化方法 → Bean可用」,完全符合Spring Bean的初始化觸發(fā)時(shí)機(jī)。
二、方式2:實(shí)現(xiàn)InitializingBean接口(Spring原生方式)
2.1 核心說明
InitializingBean是Spring框架提供的原生接口,僅包含一個(gè)抽象方法:void afterPropertiesSet() throws Exception。當(dāng)Bean實(shí)現(xiàn)該接口后,Spring容器在完成Bean實(shí)例創(chuàng)建和屬性注入后,會(huì)自動(dòng)調(diào)用afterPropertiesSet()方法執(zhí)行初始化邏輯。
特點(diǎn):與Spring框架強(qiáng)耦合(Bean直接依賴Spring API),但執(zhí)行時(shí)機(jī)精準(zhǔn),無需額外配置,適合需要深度對(duì)接Spring生命周期的場(chǎng)景。
2.2 完整代碼示例
基于方式1的基礎(chǔ)代碼修改,僅需調(diào)整UserService類(實(shí)現(xiàn)InitializingBean接口),其他類(UserDao、SpringConfig、測(cè)試類)保持不變。
// 目標(biāo)Bean:UserService(實(shí)現(xiàn)InitializingBean接口)
import org.springframework.beans.factory.InitializingBean;
public class UserService implements InitializingBean {
private final UserDao userDao;
// 構(gòu)造器注入U(xiǎn)serDao
public UserService(UserDao userDao) {
this.userDao = userDao;
System.out.println("UserService構(gòu)造方法執(zhí)行:已注入U(xiǎn)serDao");
}
// 重寫InitializingBean的afterPropertiesSet()方法:Spring自動(dòng)調(diào)用
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean初始化方法(afterPropertiesSet)執(zhí)行:");
System.out.println(" - 初始化用戶服務(wù)核心組件");
System.out.println(" - 校驗(yàn)UserDao依賴是否正??捎?);
System.out.println(" - 加載用戶權(quán)限配置");
}
// 模擬業(yè)務(wù)方法
public void getUserInfo() {
System.out.println("執(zhí)行用戶查詢業(yè)務(wù):依賴UserDao完成數(shù)據(jù)獲取");
}
}
2.3 運(yùn)行結(jié)果與解讀
UserDao實(shí)例已創(chuàng)建 UserService構(gòu)造方法執(zhí)行:已注入U(xiǎn)serDao InitializingBean初始化方法(afterPropertiesSet)執(zhí)行: - 初始化用戶服務(wù)核心組件 - 校驗(yàn)UserDao依賴是否正??捎? - 加載用戶權(quán)限配置 === Spring容器啟動(dòng)完成 === 執(zhí)行用戶查詢業(yè)務(wù):依賴UserDao完成數(shù)據(jù)獲取
解讀:無需額外注解或配置,僅通過實(shí)現(xiàn)接口即可完成初始化邏輯的自動(dòng)執(zhí)行,適合Spring專屬項(xiàng)目的開發(fā)場(chǎng)景。但需注意:若后續(xù)更換框架,該Bean需修改代碼(解耦成本高)。
三、方式3:通過init-method配置自定義初始化方法
3.1 核心說明
init-method是Spring提供的“配置式”初始化方式,支持兩種配置形式:① 注解配置(@Bean注解的initMethod屬性);② XML配置(傳統(tǒng)Spring項(xiàng)目常用)。該方式允許開發(fā)者自定義初始化方法名(無參數(shù)、無返回值),通過配置指定后,Spring容器會(huì)在Bean依賴注入完成后調(diào)用該方法。
特點(diǎn):完全解耦(不依賴任何注解或接口),靈活性最高,適合傳統(tǒng)項(xiàng)目、舊代碼兼容或需要自定義方法名的場(chǎng)景。
3.2 代碼示例1:注解配置(@Bean(initMethod = “方法名”))
核心修改:UserService類中定義自定義初始化方法,SpringConfig類中通過@Bean指定initMethod屬性。
// 1. 目標(biāo)Bean:UserService(含自定義初始化方法)
public class UserService {
private final UserDao userDao;
// 構(gòu)造器注入U(xiǎn)serDao
public UserService(UserDao userDao) {
this.userDao = userDao;
System.out.println("UserService構(gòu)造方法執(zhí)行:已注入U(xiǎn)serDao");
}
// 自定義初始化方法(方法名可任意,如init、customInit等)
public void userServiceInit() {
System.out.println("init-method(注解配置)初始化方法執(zhí)行:");
System.out.println(" - 初始化用戶模塊日志組件");
System.out.println(" - 加載用戶業(yè)務(wù)規(guī)則配置");
System.out.println(" - 初始化用戶數(shù)據(jù)統(tǒng)計(jì)緩存");
}
// 模擬業(yè)務(wù)方法
public void getUserInfo() {
System.out.println("執(zhí)行用戶查詢業(yè)務(wù):依賴UserDao完成數(shù)據(jù)獲取");
}
}
// 2. Spring配置類:通過@Bean指定initMethod
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
@Bean
public UserDao userDao() {
UserDao userDao = new UserDao();
userDao.initDao();
return userDao;
}
// 關(guān)鍵:通過initMethod屬性指定自定義初始化方法名(userServiceInit)
@Bean(initMethod = "userServiceInit")
public UserService userService(UserDao userDao) {
return new UserService(userDao);
}
}
運(yùn)行結(jié)果(其他類不變):
UserDao實(shí)例已創(chuàng)建 UserService構(gòu)造方法執(zhí)行:已注入U(xiǎn)serDao init-method(注解配置)初始化方法執(zhí)行: - 初始化用戶模塊日志組件 - 加載用戶業(yè)務(wù)規(guī)則配置 - 初始化用戶數(shù)據(jù)統(tǒng)計(jì)緩存 === Spring容器啟動(dòng)完成 === 執(zhí)行用戶查詢業(yè)務(wù):依賴UserDao完成數(shù)據(jù)獲取
3.3 代碼示例2:XML配置(傳統(tǒng)Spring項(xiàng)目)
對(duì)于使用XML配置的傳統(tǒng)Spring項(xiàng)目(如SSM框架),可通過標(biāo)簽的init-method屬性指定初始化方法。核心修改:移除注解配置,編寫XML配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.example.UserDao"></bean>
<bean id="userService" class="com.example.UserService" init-method="userServiceInit">
<constructor-arg ref="userDao"/>
</bean>
</beans>
// 2. 測(cè)試類(加載XML配置啟動(dòng)容器)
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanInitXmlTest {
public static void main(String[] args) {
// 加載XML配置文件啟動(dòng)Spring容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("=== Spring容器啟動(dòng)完成 ===");
// 獲取Bean并驗(yàn)證
UserService userService = context.getBean(UserService.class);
userService.getUserInfo();
// 關(guān)閉容器
context.close();
}
}
運(yùn)行結(jié)果與注解配置一致,適合不使用注解的傳統(tǒng)項(xiàng)目場(chǎng)景。
四、關(guān)鍵補(bǔ)充:三種方式的執(zhí)行順序驗(yàn)證
若同一Bean同時(shí)使用三種初始化方式,執(zhí)行順序如何?我們通過代碼驗(yàn)證(核心修改UserService類,同時(shí)包含三種方式):
import org.springframework.beans.factory.InitializingBean;
import javax.annotation.PostConstruct;
public class UserService implements InitializingBean {
private final UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
System.out.println("1. UserService構(gòu)造方法執(zhí)行");
}
// 方式1:@PostConstruct
@PostConstruct
public void initByPostConstruct() {
System.out.println("2. @PostConstruct初始化方法執(zhí)行");
}
// 方式2:InitializingBean
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("3. InitializingBean(afterPropertiesSet)執(zhí)行");
}
// 方式3:init-method自定義方法
public void userServiceInit() {
System.out.println("4. init-method初始化方法執(zhí)行");
}
}
SpringConfig配置:@Bean(initMethod = "userServiceInit"),運(yùn)行結(jié)果:
UserDao實(shí)例已創(chuàng)建 1. UserService構(gòu)造方法執(zhí)行 2. @PostConstruct初始化方法執(zhí)行 3. InitializingBean(afterPropertiesSet)執(zhí)行 4. init-method初始化方法執(zhí)行 === Spring容器啟動(dòng)完成 ===
結(jié)論:執(zhí)行順序固定為 → @PostConstruct(1)→ InitializingBean(2)→ init-method(3)。
五、三種方式對(duì)比表(實(shí)戰(zhàn)選型依據(jù))
| 初始化方式 | 執(zhí)行順序 | 核心優(yōu)勢(shì) | 潛在缺點(diǎn) | 適用場(chǎng)景 |
|---|---|---|---|---|
| @PostConstruct注解 | 1(最先) | 1. 基于JSR-250規(guī)范,跨框架通用;2. 低耦合(不依賴Spring);3. 使用簡(jiǎn)單(僅需注解) | JDK9+需手動(dòng)引入jakarta.annotation-api依賴 | 絕大多數(shù)注解式開發(fā)場(chǎng)景(如Spring Boot項(xiàng)目),追求框架解耦 |
| 實(shí)現(xiàn)InitializingBean接口 | 2(中間) | 1. Spring原生支持,執(zhí)行時(shí)機(jī)精準(zhǔn);2. 無需額外配置(僅實(shí)現(xiàn)接口) | 1. 與Spring強(qiáng)耦合;2. 代碼侵入性高(Bean需實(shí)現(xiàn)特定接口) | 需要深度對(duì)接Spring生命周期,或Spring專屬項(xiàng)目 |
| init-method配置(注解/XML) | 3(最后) | 1. 完全解耦(無注解/接口依賴);2. 靈活性最高(自定義方法名);3. 支持XML/注解雙配置 | 1. 注解配置需手動(dòng)指定方法名(易寫錯(cuò));2. XML配置較繁瑣 | 1. 傳統(tǒng)XML配置項(xiàng)目;2. 舊代碼兼容;3. 需自定義初始化方法名的場(chǎng)景 |
六、實(shí)戰(zhàn)注意事項(xiàng)(避坑指南)
- 初始化方法需滿足:無參數(shù)、無返回值,且不能是靜態(tài)方法(static修飾會(huì)導(dǎo)致Spring無法調(diào)用)。
- @PostConstruct依賴問題:JDK9及以上版本需引入jakarta.annotation-api依賴,否則會(huì)報(bào)“找不到@PostConstruct注解”錯(cuò)誤。
- 避免在構(gòu)造器中執(zhí)行初始化邏輯:構(gòu)造器僅負(fù)責(zé)屬性注入,此時(shí)Bean尚未完全創(chuàng)建,若執(zhí)行業(yè)務(wù)邏輯可能導(dǎo)致依賴未就緒(如NullPointerException)。
- init-method方法名匹配:注解配置(@Bean(initMethod))或XML配置時(shí),方法名需與Bean中的自定義方法名完全一致(大小寫敏感),否則Spring無法識(shí)別。
- 多初始化方式共存:實(shí)際開發(fā)中不建議同一Bean同時(shí)使用多種方式(易造成邏輯混亂),優(yōu)先選擇一種符合項(xiàng)目技術(shù)棧的方式即可。
七、總結(jié)
本文詳細(xì)講解了Spring Bean初始化的三種核心方式,結(jié)合完整代碼示例、執(zhí)行順序驗(yàn)證、場(chǎng)景對(duì)比及避坑指南,可總結(jié)為:
- 優(yōu)先選擇:@PostConstruct注解(通用性強(qiáng)、低耦合,適合Spring Boot等注解式項(xiàng)目);
- 特殊場(chǎng)景:實(shí)現(xiàn)InitializingBean接口(Spring專屬項(xiàng)目,需精準(zhǔn)對(duì)接生命周期);
- 兼容場(chǎng)景:init-method配置(傳統(tǒng)XML項(xiàng)目、舊代碼兼容,完全解耦)。
掌握這三種方式的核心邏輯和適用場(chǎng)景,能幫助你在實(shí)際開發(fā)中靈活處理Bean的初始化需求,避免踩坑。若需進(jìn)一步學(xué)習(xí)Bean的銷毀方式(如@PreDestroy、DisposableBean接口、destroy-method配置),可留言補(bǔ)充~
以上就是Spring Bean初始化的三種常用方式(易懂版)的詳細(xì)內(nèi)容,更多關(guān)于Spring Bean初始化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JAVA操作MongoDB數(shù)據(jù)庫實(shí)例教程
MongoDB是一個(gè)文檔型數(shù)據(jù)庫,是NOSQL家族中最重要的成員之一,下面這篇文章主要給大家介紹了關(guān)于JAVA操作MongoDB數(shù)據(jù)庫的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05
UniApp?+?SpringBoot?實(shí)現(xiàn)支付寶支付和退款功能
這篇文章主要介紹了UniApp?+?SpringBoot?實(shí)現(xiàn)支付寶支付和退款功能,基本的?SpringBoot?的腳手架,可以去IDEA?自帶的快速生成腳手架插件,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-06-06
SpringBoot+Flying Saucer+Thymeleaf實(shí)現(xiàn)PDF生成的完整指南
在實(shí)際開發(fā)中,PDF生成是常見需求,本文將詳細(xì)講解如何基于 Spring Boot + Flying Saucer + Thymeleaf 實(shí)現(xiàn) PDF 生成,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2026-01-01
Java.lang.OutOfMemoryError: GC overhead limit
本文主要介紹了Java.lang.OutOfMemoryError: GC overhead limit exceeded錯(cuò)誤的解決,錯(cuò)誤是由于堆空間不足導(dǎo)致GC頻繁運(yùn)行,從而引起的,下面就來介紹一下解決方法2025-03-03
解決feign之間文件上傳報(bào)錯(cuò):Error converting request body
這篇文章主要介紹了解決feign之間文件上傳報(bào)錯(cuò):Error converting request body的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-05-05

