Spring 這六種初始化 Bean 的方式,個(gè)個(gè)是精華!
作為一個(gè) Java開(kāi)發(fā)工程師,Spring應(yīng)該是接觸最多的一個(gè)框架,而 Bean又是 Spring的基石。那么,在 Spring中,有多少種 Bean初始化的方式,這些方式有什么優(yōu)缺點(diǎn)?我們?cè)撊绾芜x擇?這篇文章,我們來(lái)聊一聊。
總體來(lái)說(shuō),Spring初始化Bean 包含以下6種方法:
1. XML配置方式
在 Spring發(fā)展初期,XML配置方式是最傳統(tǒng)也是最流行的初始化方式,盡管如今大家更多選擇注解方式,但了解這個(gè)"祖?zhèn)魇炙?還是很有必要的。
如下示例,展示了如何使用XML配置初始化和銷(xiāo)毀方法:
<bean id="testService" class="com.yuanjava.TestService" init-method="init" destroy-method="cleanup"/>
對(duì)應(yīng)的Java類(lèi):
public class TestService {
public void init() {
System.out.println("XML配置的init方法被調(diào)用啦!");
}
public void cleanup() {
System.out.println("XML配置的destroy方法被調(diào)用啦!");
}
}
優(yōu)點(diǎn):
- 集中式管理:一個(gè)XML文件就可以管理多個(gè)Bean的初始化和銷(xiāo)毀邏輯。
- 修改無(wú)需重新編譯:直接改了XML配置重啟就行,不用重新打包部署
- 解耦性極強(qiáng):配置和實(shí)現(xiàn)完全分離的方式,特別適合需要頻繁切換實(shí)現(xiàn)的場(chǎng)景
- 歷史兼容性好:早期的Spring版本也支持XML配置,不影響現(xiàn)有的項(xiàng)目
缺點(diǎn):
- 配置冗長(zhǎng):XML配置文件比較冗長(zhǎng),維護(hù)成本大
- 類(lèi)型不安全:編譯期不報(bào)錯(cuò),如果XML配置有錯(cuò)誤,需要運(yùn)行時(shí)會(huì)報(bào)錯(cuò)
- 重構(gòu)困難: 當(dāng)你重命名一個(gè)類(lèi)時(shí),IDE不會(huì)自動(dòng)更新XML中的class屬性
思考題:有沒(méi)有小伙伴還記得,為什么我們那時(shí)候要在 XML里配init-method,而不是直接在類(lèi)里寫(xiě)個(gè)構(gòu)造方法呢?(答案后面揭曉)
2. 注解方式
隨著 Spring 生態(tài)的發(fā)展,特別是 Spring Boot的普及,注解方式已經(jīng)才能開(kāi)發(fā)者的標(biāo)配,下面是一個(gè)簡(jiǎn)單的示例:
@Component
public class TestService {
@PostConstruct
public void postConstruct() {
System.out.println("@PostConstruct方法執(zhí)行了");
}
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy方法執(zhí)行了");
}
}
優(yōu)點(diǎn):
- 代碼即配置:只需要寫(xiě)注解,就能完成初始化和銷(xiāo)毀邏輯
- 強(qiáng)大的IDE支持:IDE可以直接幫你生成這兩個(gè)方法,無(wú)需手動(dòng)寫(xiě)
- 類(lèi)型安全:編譯期檢查,IDE會(huì)報(bào)錯(cuò),防止出錯(cuò)
缺點(diǎn):
- 分散式配置:一個(gè)類(lèi)只能管理一個(gè)Bean的初始化和銷(xiāo)毀邏輯,不夠集中
- 修改需要重新編譯:直接改了Java代碼,需要重新打包部署
- 運(yùn)行時(shí)開(kāi)銷(xiāo):?jiǎn)?dòng)時(shí)Spring需要掃描所有注解,會(huì)造成一定的性能損耗
3. InitializingBean接口
如果你想玩深度,那么InitializingBean接口絕對(duì)是首選,它是 Spring的親兒子,這個(gè)接口中定義了一個(gè)方法:
@Component
public class TestService implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean的afterPropertiesSet方法被調(diào)用");
}
}
優(yōu)點(diǎn):
- 絕對(duì)執(zhí)行順序保證:只要實(shí)現(xiàn)了InitializingBean接口,就能保證初始化的順序
- 框架原生支持:Spring框架本身就支持InitializingBean,Spring的親兒子待遇
- 明確契約:實(shí)現(xiàn)接口是一種顯式的契約聲明
缺點(diǎn):
- 單一方法限制:只能實(shí)現(xiàn)一個(gè)初始化方法,不夠靈活
- 異常處理尷尬:只能拋出異常,無(wú)法返回值,不夠靈活
雖然這種方式很直接,但因?yàn)樗汛a和 Spring框架耦合在一起了,所以現(xiàn)在不太推薦使用。不過(guò)了解它有助于我們理解 Spring的原理。
4. @Bean的 initMethod屬性
@Bean的 initMethod屬性采用了配置類(lèi)的玩法,示例代碼如下:
@Configuration
publicclass AppConfig {
@Bean(initMethod = "init", destroyMethod = "cleanup")
public FancyService fancyService() {
returnnew FancyService();
}
}
publicclass FancyService {
public void init() {
System.out.println("@Bean的initMethod指定的方法");
}
public void cleanup() {
System.out.println("@Bean的destroyMethod指定的方法");
}
}
優(yōu)點(diǎn):
- 無(wú)侵入性:不需要改動(dòng)原來(lái)的類(lèi),只需要改動(dòng)配置文件,就能完成初始化和銷(xiāo)毀邏輯
- 統(tǒng)一生命周期管理:所有Bean的生命周期方法名在配置處一目了然,特別適合需要嚴(yán)格規(guī)范的中大型項(xiàng)目
缺點(diǎn):
- 方法名硬編碼:全部通過(guò) initMethod = "xxx"命名,存在重構(gòu)風(fēng)險(xiǎn)
- 調(diào)試?yán)щy:initMethod的調(diào)用被Spring代理層層包裹
大家有沒(méi)有注意到,這里的 destroyMethod有個(gè)隱藏特性?如果我把cleanup方法改個(gè)名,但不改destroyMethod配置,會(huì)發(fā)生什么?
5. BeanPostProcessor
這個(gè)可就厲害了,它能插手所有 Bean的初始化過(guò)程:
@Component
publicclass TestProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("Before初始化: " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("After初始化: " + beanName);
return bean;
}
}
優(yōu)點(diǎn):
- 全局控制:可以使用該技術(shù)在不修改業(yè)務(wù)代碼的情況下,為整個(gè)系統(tǒng)添加了方法調(diào)用日志
- AOP基礎(chǔ):Spring AOP就是通過(guò)BeanPostProcessor實(shí)現(xiàn)的(具體是AbstractAutoProxyCreator)
缺點(diǎn):
- 性能損耗:要求所有 BeanPostProcessor必須加@Order和嚴(yán)格的異常處理
- 調(diào)試?yán)щy:復(fù)雜的調(diào)用棧
6. @EventListener
Spring的@EventListener事件機(jī)制也可以用來(lái)做初始化:
@Component
public class EventInitService {
@EventListener(ContextRefreshedEvent.class)
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("容器刷新完畢,開(kāi)始執(zhí)行初始化邏輯");
}
}
優(yōu)點(diǎn):
- 松耦合設(shè)計(jì):事件發(fā)布者和監(jiān)聽(tīng)者完全解耦
- 靈活監(jiān)聽(tīng):支持多事件類(lèi)型、條件過(guò)濾
- 異步支持:簡(jiǎn)單注解即可實(shí)現(xiàn)異步處理
- 順序控制:通過(guò)@Order指定監(jiān)聽(tīng)順序
缺點(diǎn):
- 調(diào)試?yán)щy:事件鏈路追蹤復(fù)雜
- 類(lèi)型安全:運(yùn)行時(shí)才能發(fā)現(xiàn)事件類(lèi)型不匹配
- 性能風(fēng)險(xiǎn):同步事件會(huì)阻塞發(fā)布者線(xiàn)程
- 事務(wù)邊界:事件處理與事務(wù)的交互需要特別注意
7. Bean初始化順序
上面,我們已經(jīng)分析了 6種初始化方式,那么,這幾種方式的順序是什么?來(lái),看一個(gè)綜合例子:
@Component
publicclass OrderDemoBean implements InitializingBean {
public OrderDemoBean() {
System.out.println("1. 構(gòu)造方法");
}
@PostConstruct
public void postConstruct() {
System.out.println("3. @PostConstruct");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("4. InitializingBean");
}
public void initMethod() {
System.out.println("5. init-method");
}
}
// 配合BeanPostProcessor的輸出,完整順序是:
// 1. 構(gòu)造方法
// 2. BeanPostProcessor的postProcessBeforeInitialization
// 3. @PostConstruct
// 4. InitializingBean
// 5. init-method
// 6. BeanPostProcessor的postProcessAfterInitialization
記憶口訣:構(gòu)造-BeforePost-@PostConstruct-AfterProperties-initMethod-AfterPost
8. 總結(jié)
本文,我們一起分析了Spring中 6種 Bean初始化的方式以及他們的優(yōu)缺點(diǎn)(未做很深的原理解析),在實(shí)際開(kāi)發(fā)中,因?yàn)槊鎸?duì)的業(yè)務(wù)需求不同,可能每種方式都會(huì)使用到,所以,作為開(kāi)發(fā)者,建議 6種方式都要掌握。