SpringBoot靜態(tài)類調(diào)用Bean的兩種方案(新手版)
一、開篇痛點(diǎn):靜態(tài)類調(diào)用Bean,小白必遇的“攔路虎”
剛接觸SpringBoot的同學(xué),大概率會(huì)碰到這個(gè)問題:
寫了個(gè)靜態(tài)工具類(比如 MyStaticUtils),想在靜態(tài)方法里調(diào)用Spring管理的Bean(比如 UserService),用 @Autowired 直接注入?yún)s報(bào) NullPointerException!
比如想實(shí)現(xiàn)“根據(jù)用戶ID查用戶名”的靜態(tài)方法,直接這么寫必錯(cuò):
// 錯(cuò)誤示范
public class MyStaticUtils {
@Autowired
private static UserService userService; // 注入失敗,永遠(yuǎn)是null
public static String getUserNameById(Long userId) {
return userService.getUserName(userId); // 運(yùn)行時(shí)空指針
}
}
核心原因還是之前說的:靜態(tài)類加載時(shí)機(jī)早于Spring Bean初始化,@Autowired 是給Spring實(shí)例注入Bean的,管不到靜態(tài)變量。
今天就給大家講兩種解決方案,重點(diǎn)對(duì)比侵入性差異,幫小白按需選擇!
二、兩方案侵入性對(duì)比(一眼看懂區(qū)別)
先上核心對(duì)比表,小白不用記復(fù)雜邏輯,看表格就知道差異:
| 對(duì)比維度 | 方案一:SpringContextHolder(中間人模式) | 方案二:@PostConstruct注解(入住模式) |
|---|---|---|
| 核心思路 | 用中間類持有Spring上下文,靜態(tài)類按需獲取Bean | 讓靜態(tài)類被Spring管理,初始化后給靜態(tài)變量賦值 |
| 代碼侵入性 | 低(靜態(tài)類無需加Spring注解,保持純靜態(tài)特性) | 高(靜態(tài)類需加@Component,依賴Spring注解) |
| 實(shí)現(xiàn)復(fù)雜度 | 中等(需新增1個(gè)中間類) | 簡單(無需新增類,僅改靜態(tài)工具類) |
| 靜態(tài)類獨(dú)立性 | 強(qiáng)(可脫離Spring單獨(dú)使用) | 弱(必須被Spring掃描管理,否則失效) |
| 適用場景 | 不想修改靜態(tài)類結(jié)構(gòu)、侵入性要求低的場景 | 靜態(tài)類可被Spring管理、邏輯簡單的場景 |
簡單說:方案一是“找中間人拿工具”,方案二是“讓靜態(tài)類住進(jìn)Spring的房子里直接拿工具”。
三、方案一:SpringContextHolder(低侵入,通用方案)
1. 核心邏輯
通過一個(gè)“中間人”(SpringContextHolder)持有Spring的上下文(ApplicationContext),靜態(tài)類需要Bean時(shí),直接問“中間人”要,不用自己依賴Spring。
2. 快速實(shí)現(xiàn)(簡要回顧,重點(diǎn)看對(duì)比)
// 1. 中間人:SpringContextHolder(直接復(fù)制可用)
@Component
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext context) {
SpringContextHolder.applicationContext = context;
}
// 靜態(tài)方法:獲取Bean
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
}
// 2. 靜態(tài)工具類調(diào)用(無需加任何Spring注解)
public class MyStaticUtils {
public static String getUserNameById(Long userId) {
// 關(guān)鍵:通過中間人獲取Bean
UserService userService = SpringContextHolder.getBean(UserService.class);
return userService.getUserName(userId);
}
}
3. 核心優(yōu)勢
靜態(tài)類還是“純靜態(tài)”,不用加 @Component,就算脫離Spring環(huán)境也能單獨(dú)使用,侵入性極低。
四、方案二:@PostConstruct注解(高侵入,簡單方案)
這就是你找到的CSDN方案,核心是“讓靜態(tài)類被Spring管理”,通過注解在Bean初始化后給靜態(tài)變量賦值,巧妙但侵入性強(qiáng)。
1. 核心原理(小白通俗理解)
- 給靜態(tài)工具類加
@Component,讓它變成Spring管理的Bean(住進(jìn)Spring的“房子”); - 用
@Autowired先給實(shí)例變量注入Bean(Spring能管理實(shí)例變量); - 用
@PostConstruct注解一個(gè)初始化方法,在Spring初始化這個(gè)工具類后,把實(shí)例變量的值賦給靜態(tài)變量; - 靜態(tài)方法直接用靜態(tài)變量調(diào)用Bean,就不會(huì)空指針了。
2. 分步實(shí)現(xiàn)(還是用“查用戶名”案例)
步驟1:創(chuàng)建Spring管理的Bean(UserService)
和之前一致,模擬查詢邏輯:
@Service
public class UserService {
// 模擬根據(jù)ID查用戶名
public String getUserName(Long userId) {
return userId == 1 ? "張三" : "未知用戶";
}
}
步驟2:改造靜態(tài)工具類(核心改造)
給工具類加 @Component,配合 @Autowired 和 @PostConstruct:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
// 關(guān)鍵1:加@Component,讓Spring管理這個(gè)工具類
@Component
public class MyStaticUtils {
// 關(guān)鍵2:先注入實(shí)例變量(Spring能識(shí)別@Autowired)
@Autowired
private UserService userService;
// 關(guān)鍵3:靜態(tài)變量(供靜態(tài)方法使用)
private static MyStaticUtils staticUtils;
// 關(guān)鍵4:@PostConstruct:Spring初始化當(dāng)前Bean后執(zhí)行(初始化順序:構(gòu)造方法→@Autowired→@PostConstruct)
@PostConstruct
public void init() {
// 把當(dāng)前實(shí)例賦值給靜態(tài)變量
staticUtils = this;
}
// 靜態(tài)方法:通過靜態(tài)變量調(diào)用Bean的方法
public static String getUserNameById(Long userId) {
// 核心:staticUtils持有實(shí)例,實(shí)例持有注入的userService
return staticUtils.userService.getUserName(userId);
}
}
步驟3:測試驗(yàn)證
寫個(gè)Controller調(diào)用:
@RestController
public class TestController {
@GetMapping("/user/{userId}")
public String getUserName(@PathVariable Long userId) {
return MyStaticUtils.getUserNameById(userId); // 調(diào)用靜態(tài)方法
}
}
啟動(dòng)項(xiàng)目訪問 http://localhost:8080/user/1,返回“張三”—— 成功!
3. 原理拆解(小白必懂)
用“房子”比喻再講一遍:
@Component讓MyStaticUtils住進(jìn)了Spring的“房子”,成為Spring管理的Bean;- Spring初始化時(shí),先執(zhí)行
@Autowired,給實(shí)例變量userService注入Bean(相當(dāng)于給房子里放了工具); - 再執(zhí)行
@PostConstruct標(biāo)注的init()方法,把當(dāng)前實(shí)例(this)賦給靜態(tài)變量staticUtils(相當(dāng)于給房子配了把“靜態(tài)鑰匙”); - 靜態(tài)方法通過
staticUtils這把鑰匙,就能拿到房子里的userService工具了。
五、方案二關(guān)鍵注意事項(xiàng)(小白避坑)
- 必須加
@Component:否則Spring不會(huì)管理這個(gè)工具類,@Autowired和@PostConstruct都無效,靜態(tài)變量還是null; - 實(shí)例變量+靜態(tài)變量配合:不能直接給靜態(tài)變量加
@Autowired(Spring不支持),必須先注入實(shí)例變量,再通過@PostConstruct賦值給靜態(tài)變量; @PostConstruct執(zhí)行時(shí)機(jī):在構(gòu)造方法之后、Bean初始化完成之前執(zhí)行,只會(huì)執(zhí)行一次,確保靜態(tài)變量只賦值一次;- 不能在靜態(tài)代碼塊中調(diào)用:靜態(tài)代碼塊加載時(shí)機(jī)早于
@PostConstruct,此時(shí)staticUtils還沒賦值,調(diào)用會(huì)空指針。
六、適用場景總結(jié)(小白怎么選?)
| 場景描述 | 推薦方案 | 理由 |
|---|---|---|
| 靜態(tài)工具類不想被Spring管理、需保持獨(dú)立性 | 方案一:SpringContextHolder | 侵入性低,工具類可脫離Spring使用 |
| 靜態(tài)工具類僅在Spring環(huán)境中使用、邏輯簡單 | 方案二:@PostConstruct | 無需新增中間類,代碼量少,實(shí)現(xiàn)簡單 |
| 項(xiàng)目中靜態(tài)工具類較多、需統(tǒng)一管理 | 方案一:SpringContextHolder | 中間類可復(fù)用,維護(hù)成本低 |
| 不想新增額外類、快速實(shí)現(xiàn)需求 | 方案二:@PostConstruct | 直接改造工具類,幾分鐘就能搞定 |
七、最終建議(小白牢記)
- 如果你是“純小白”,只是想快速實(shí)現(xiàn)需求,且靜態(tài)工具類只在Spring項(xiàng)目中用,選方案二(
@PostConstruct),代碼簡單不用記中間類; - 如果你想讓代碼更規(guī)范、侵入性更低,或者靜態(tài)工具類可能脫離Spring使用,選方案一(
SpringContextHolder),通用性更強(qiáng); - 兩種方案的核心都是“解決靜態(tài)類和Spring Bean加載時(shí)機(jī)不匹配的問題”,只是實(shí)現(xiàn)思路不同,沒有絕對(duì)優(yōu)劣,按需選擇即可。
其實(shí)這兩種方案本質(zhì)是“取舍”:方案二用“侵入性”換“簡單性”,方案一用“多一個(gè)中間類”換“低侵入性”。學(xué)會(huì)這兩種,以后遇到靜態(tài)類調(diào)用Bean的問題,再也不用一頭霧水啦!
以上就是SpringBoot靜態(tài)類調(diào)用Bean的兩種方案(新手版)的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot靜態(tài)類調(diào)用Bean的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
feign客戶端設(shè)置超時(shí)時(shí)間操作
這篇文章主要介紹了feign客戶端設(shè)置超時(shí)時(shí)間操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09
java集合中HashSet LinkedHashSet TreeSet三者異同面試精講
這篇文章主要為大家介紹了java集合中HashSet LinkedHashSet TreeSet三者異同面試精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
Sublime Text 打開Java文檔中文亂碼的解決方案
這篇文章主要介紹了Sublime Text 中文亂碼的解決方案,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2020-12-12
基于Spring-cloud-gateway實(shí)現(xiàn)全局日志記錄的方法
最近項(xiàng)目在線上運(yùn)行出現(xiàn)了一些難以復(fù)現(xiàn)的bug需要定位相應(yīng)api的日志,通過nginx提供的api請(qǐng)求日志難以實(shí)現(xiàn),于是在gateway通過全局過濾器記錄api請(qǐng)求日志,本文給大家介紹基于Spring-cloud-gateway實(shí)現(xiàn)全局日志記錄,感興趣的朋友一起看看吧2023-11-11
如何使用Spring Boot ApplicationRunner解析命令行中的參數(shù)
這篇文章主要介紹了使用Spring Boot ApplicationRunner解析命令行中的參數(shù),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-12-12
SpringCloud?Gateway?DispatcherHandler調(diào)用方法詳細(xì)介紹
我們第一個(gè)關(guān)注的類就是DispatcherHandler,這個(gè)類提供的handle()方法,封裝了我們之后所有的handlerMappings,這個(gè)DispatcherHandler有點(diǎn)想SpringMVC的DispatchServlet,里面也是封裝了請(qǐng)求和對(duì)應(yīng)的處理方法的關(guān)系2022-10-10

