Spring依賴注入Bean流程深入解析
1-注入方式
常見(jiàn)的方式:
一個(gè)類注入另一個(gè)類,其實(shí)本質(zhì)都是使用的構(gòu)造函數(shù),這里以@RequiredArgsConstructor舉例,常見(jiàn)的也有@AllArgsConstructor本質(zhì)相差無(wú)幾
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
private final UserService userService;
}
@RequiredArgsConstructor:會(huì)為所有 final 修飾的字段生成構(gòu)造函數(shù)參數(shù)
//等價(jià)于
@Configuration
public class SecurityConfig {
private final UserService userService;
public SecurityConfig(UserService userService) {
this.userService = userService;
}
}優(yōu)點(diǎn):
- 使用 final 關(guān)鍵字確保依賴不會(huì)被意外修改
- 利用Lombok:減少重復(fù)代碼,提高開(kāi)發(fā)效率
推薦使用 @RequiredArgsConstructor + final 字段
不建議使用:@Autowired、@Resource等方式
2-生命周期
在singleton下:
SecurityConfig Bean 生命周期 == UserService Bean 生命周期
- 創(chuàng)建時(shí)注入
- 容器不銷毀 → 引用一直有效
- 不會(huì)被替換
- 不會(huì)重新注入
運(yùn)行時(shí)等價(jià)于:
// 在存在 AOP(如 @Transactional)時(shí),為代理對(duì)象 private final UserService userService = UserService$$Proxy@7a3f21;
3-代理模式
3-1.沒(méi)有代理
正常寫業(yè)務(wù)
public interface UserService {
void save();
}
public class UserServiceImpl implements UserService {
public void save() {
System.out.println("保存用戶");
}
}調(diào)用方:
UserService userService = new UserServiceImpl(); userService.save();
3-2.簡(jiǎn)單代理
public class UserServiceProxy implements UserService {
private final UserService target;
public UserServiceProxy(UserService target) {
this.target = target;
}
@Override
public void save() {
System.out.println("開(kāi)啟事務(wù)");
try {
target.save();
System.out.println("提交事務(wù)");
} catch (Exception e) {
System.out.println("回滾事務(wù)");
throw e;
}
}
}使用:
UserService userService = new UserServiceProxy(new UserServiceImpl()); userService.save();
用一個(gè)“長(zhǎng)得一樣”的對(duì)象,包住真正對(duì)象,在調(diào)用前后加邏輯
有點(diǎn)像包裝類,但是本質(zhì)不同:
- 為了給這個(gè)對(duì)象多加點(diǎn)功能(如加密、壓縮、緩存結(jié)果),那是包裝,能訪問(wèn)到本體,且自己親自放進(jìn)包裝類中
- 為了控制權(quán)限、延遲加載、單例保證,那是代理,且訪問(wèn)不到本體
- 包裝:我?guī)湍愣嘧鳇c(diǎn)事;代理:你只能通過(guò)我做這件事
3-3.Spring的代理
Spring的代理 = 自動(dòng)生成的代理類,Spring只是幫你自動(dòng)干了這件事
UserService proxy =
(UserService) Proxy.newProxyInstance(
loader,
new Class[]{UserService.class},
(obj, method, args) -> {
System.out.println("開(kāi)啟事務(wù)");
Object result = method.invoke(target, args);
System.out.println("提交事務(wù)");
return result;
}
);4-注入流程
4-1.概念
【啟動(dòng)期】 1. 掃描 BeanDefinition 2. 創(chuàng)建原始對(duì)象 3. BeanPostProcessor 判斷是否命中 Advisor(如@Transactional相關(guān)注解) 4. 命中 → 用代理對(duì)象替代原始對(duì)象 5. 代理 / 原始對(duì)象作為最終 Bean 放入 IOC(singleton) 【運(yùn)行期】 6. 所有依賴注入,拿到的都是這個(gè)最終 Bean 7. 所有 AOP 能力,只存在于代理對(duì)象上
只有涉及到增強(qiáng)行為才會(huì)創(chuàng)建代理類,比如你在方法上包裹@Transactional,或者你自定義的@Aspect,以及@Async / @Cacheable等等
4-2.示例
@Configuration
public class SecurityConfig {
private final UserService userService;// Bean 實(shí)例(通常是代理對(duì)象)的引用,并且在該 Bean 生命周期內(nèi)長(zhǎng)期存在
public SecurityConfig(UserService userService) {
this.userService = userService;
}
public void test(User user){
userService.saveData(user);
}
}
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
private final UserMapper userMapper;
@Transactional
public void saveData(User user){
userMapper.save(user);
}
}這樣的代碼:
- Spring會(huì)首先在IOC容器中,按類型(by type)解析依賴,找到所有符合UserService的Bean,選出一個(gè)(或一組),注入(如果存在多個(gè)實(shí)現(xiàn),會(huì)用
@Primary或@Qualifier,否則直接報(bào)錯(cuò)) - 這里因?yàn)閁serServiceImpl內(nèi)部掛上了@Transactional注解,所以創(chuàng)建了UserServiceImpl的實(shí)例后,就創(chuàng)建一個(gè)代理的Bean,即Proxy(UserServiceImpl),后續(xù)的注入都將使用代理的Bean
具體如下:
1. 解析 BeanDefinition(UserServiceImpl) 2. new UserServiceImpl() ← 原始對(duì)象 3. BeanPostProcessor 檢查 @Transactional 4. 命中事務(wù)切點(diǎn) 5. 創(chuàng)建代理對(duì)象 Proxy(UserServiceImpl) 6. 將代理對(duì)象注冊(cè)為 UserServiceImpl 的最終 Bean 實(shí)例
5-構(gòu)造函數(shù)的參數(shù)
是Spring在創(chuàng)建Bean時(shí)會(huì)調(diào)用其構(gòu)造函數(shù),然后根據(jù)構(gòu)造函數(shù)的參數(shù),會(huì)進(jìn)行注入:
@Component
@Slf4j
public class PushDataStrategyManager {
private final Map<Integer, PushDataStrategy> strategyMap = new HashMap<>();
// 這里構(gòu)造函數(shù)的參數(shù)是List<PushDataStrategy> strategies,Spring會(huì)找出所有相關(guān)的Bean放進(jìn)來(lái)
public PushDataStrategyManager(List<PushDataStrategy> strategies) {
// 自動(dòng)注冊(cè)所有策略實(shí)現(xiàn)
for (PushDataStrategy strategy : strategies) {
strategyMap.put(strategy.getType(), strategy);
log.info("注冊(cè)推送策略: type={}, class={}", strategy.getType(), strategy.getClass().getSimpleName());
}
}
}常見(jiàn)的Spring支持的構(gòu)造函數(shù)如下,Spring會(huì)自動(dòng)的進(jìn)行操作:
| 構(gòu)造參數(shù) | Spring的行為 |
|---|---|
PushDataStrategy | 按類型查找Bean;必須唯一,否則報(bào)錯(cuò) |
List<PushDataStrategy> | 查找容器中所有PushDataStrategy類型的Bean,按順序注入 |
Set<PushDataStrategy> | 同上,不保證順序 |
Map<String, PushDataStrategy> | key = BeanName,value = Bean 實(shí)例 |
具體流程:
當(dāng) Spring 創(chuàng)建 PushDataStrategyManager 時(shí):
- 解析構(gòu)造函數(shù)參數(shù)
public PushDataStrategyManager(List<PushDataStrategy> strategies) // Spring通過(guò)反射拿到泛型信息 // 參數(shù)類型:List // 泛型參數(shù):PushDataStrategy
- 發(fā)現(xiàn)這是一個(gè)
集合類型依賴
isCollectionType(parameterType)// 是 List
- 解析集合的泛型元素類型
ResolvableType.forConstructorParameter(...) // 得到 elementType = PushDataStrategy.class
- 去IOC容器中查找所有候選Bean
// 等價(jià)于 applicationContext.getBeansOfType(PushDataStrategy.class)
只要同時(shí)滿足下方條件就收集:
- 是 Bean(
@Component/@Service/@Bean) - 類型是
PushDataStrategy或其子類 - 沒(méi)被
@Conditional排除 - 沒(méi)被
@Profile排除
- 排序(如果有順序規(guī)則)
AnnotationAwareOrderComparator.sort(list)
- 注入到構(gòu)造函數(shù)
// 最終等價(jià)于: new PushDataStrategyManager(allStrategies);
6-創(chuàng)建Bean流程
大致的創(chuàng)建流程:
@Component
public class TestStrategy implements PushDataStrategy {}
啟動(dòng)期-注冊(cè)階段:
BeanDefinition: - beanName = "testStrategy" # BeanName默認(rèn)為類名首字母小寫 - beanClass = TestStrategy.class - scope = singleton - lazy = false - autowire = constructor # 此時(shí) 還沒(méi)有 this 實(shí)例地址 # Spring 存的是 BeanName → BeanDefinition(包含 class 信息)
IOC內(nèi)部結(jié)構(gòu),Spring 核心容器本質(zhì)是兩張表:
- BeanDefinition Map(啟動(dòng)期)
Map<String, BeanDefinition>
"testStrategy" -> BeanDefinition(TestStrategy.class)
也可以去直接看代碼,查看啟動(dòng)期注冊(cè)信息,示例:
// Springboot主啟動(dòng)類:
@SpringBootApplication // SpringBoot 核心注解,標(biāo)記這是一個(gè)啟動(dòng)類
@EnableDiscoveryClient // 開(kāi)啟 Nacos 注冊(cè)發(fā)現(xiàn)
@ComponentScan(basePackages = {"org.myproject.user", "org.myproject.common"})// 公共模塊掃描
public class UserServiceApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(UserServiceApplication.class, args);
// debug查看beanDefinitionMap
// 獲取 BeanFactory
DefaultListableBeanFactory beanFactory =
(DefaultListableBeanFactory) context.getBeanFactory();
// 查看所有 BeanDefinition 名稱
String[] beanNames = beanFactory.getBeanDefinitionNames();
System.out.println("BeanDefinition 數(shù)量:" + beanNames.length);
// 注意bean名字是類名的小寫
BeanDefinition bd = beanFactory.getBeanDefinition("userServiceImpl");
// 查看某一個(gè) BeanDefinition 的“元信息”
System.out.println(bd.getBeanClassName());
System.out.println(bd.getScope());
System.out.println(bd.isLazyInit());
// 看到類似:
//org.myproject.user.service.impl.UserServiceImpl
//singleton
//false
}
}
- Singleton Objects Map(運(yùn)行期)
Map<String, Object>
"testStrategy" -> TestStrategy@5f3a4d
也可以去直接看代碼,查看真正的實(shí)例,示例:
@SpringBootApplication // SpringBoot 核心注解,標(biāo)記這是一個(gè)啟動(dòng)類
@EnableDiscoveryClient // 開(kāi)啟 Nacos 注冊(cè)發(fā)現(xiàn)
@ComponentScan(basePackages = {"org.myproject.user", "org.myproject.common"})// 公共模塊掃描
public class UserServiceApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(UserServiceApplication.class, args);
DefaultListableBeanFactory beanFactory =
(DefaultListableBeanFactory) context.getBeanFactory();
// 強(qiáng)轉(zhuǎn)為可訪問(wèn)單例池的類型
DefaultSingletonBeanRegistry registry =
(DefaultSingletonBeanRegistry) beanFactory;
// 查看當(dāng)前已經(jīng)創(chuàng)建的單例 Bean
String[] singletonNames = registry.getSingletonNames();
System.out.println("已創(chuàng)建的 singleton 數(shù)量:" + singletonNames.length);
// 查看某個(gè) Bean 的真實(shí)實(shí)例
Object bean = registry.getSingleton("userServiceImpl");
System.out.println(bean);
System.out.println(bean.getClass());
//已創(chuàng)建的 singleton 數(shù)量:410
//org.myproject.user.service.impl.UserServiceImpl@6f26e775
//class org.myproject.user.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$9d7c1b34
//user使用了事務(wù)注解,會(huì)創(chuàng)建代理類
//包含 $$EnhancerBySpringCGLIB$$ 標(biāo)識(shí)
//這是 Spring CGLIB 動(dòng)態(tài)代理的典型命名模式
}
}
7-創(chuàng)建順序
實(shí)際創(chuàng)建順序假設(shè):
@Component
public class PushDataStrategyManager {
public PushDataStrategyManager(List<PushDataStrategy> strategies) {}
}
Spring 執(zhí)行流程:
- 需要?jiǎng)?chuàng)建
PushDataStrategyManager
→ 發(fā)現(xiàn)構(gòu)造器參數(shù): List<PushDataStrategy>
- Spring嘗試解析這個(gè)參數(shù)
→ 發(fā)現(xiàn)需要: 所有 PushDataStrategy 類型的 Bean
- Spring去查容器中已有的BeanDefinition
發(fā)現(xiàn): testStrategy emailStrategy smsStrategy
- 如果這些Bean尚未實(shí)例化
Spring 會(huì): 先創(chuàng)建它們(遞歸)
- 將這些實(shí)例組成List
List.of(testStrategy, emailStrategy, smsStrategy)
- 調(diào)用Manager構(gòu)造器
new PushDataStrategyManager(list)
- Manager實(shí)例化完成
總結(jié):
- Spring不是先“批量創(chuàng)建所有Bean”,而是在創(chuàng)建某個(gè)Bean時(shí),遞歸創(chuàng)建它所依賴的Bean
- 但由于:
singleton、non-lazy,看起來(lái)像是“啟動(dòng)時(shí)全部創(chuàng)建完”
到此這篇關(guān)于Spring依賴注入Bean流程及其理解的文章就介紹到這了,更多相關(guān)Spring依賴注入Bean內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java判斷一個(gè)時(shí)間是否在當(dāng)前時(shí)間區(qū)間代碼示例
這篇文章主要給大家介紹了關(guān)于使用Java判斷一個(gè)時(shí)間是否在當(dāng)前時(shí)間區(qū)間的相關(guān)資料,在日常開(kāi)發(fā)中我們經(jīng)常會(huì)涉及到時(shí)間的大小比較或者是判斷某個(gè)時(shí)間是否在某個(gè)時(shí)間段內(nèi),需要的朋友可以參考下2023-07-07
Java實(shí)現(xiàn)Excel數(shù)據(jù)導(dǎo)出的實(shí)戰(zhàn)指南
Excel 導(dǎo)出是后臺(tái)管理系統(tǒng)中的標(biāo)配功能,用于將系統(tǒng)數(shù)據(jù)導(dǎo)出為 Excel 文件供用戶下載、分析或歸檔,本文提供了一個(gè)完整的Excel數(shù)據(jù)導(dǎo)出實(shí)戰(zhàn)指南,主要針對(duì)后臺(tái)管理系統(tǒng)中的員工信息導(dǎo)出場(chǎng)景,有需要的小伙伴可以了解下2026-06-06
MyBatis?Plus如何實(shí)現(xiàn)獲取自動(dòng)生成主鍵值
這篇文章主要介紹了MyBatis?Plus如何實(shí)現(xiàn)獲取自動(dòng)生成主鍵值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
SpringBoot 整合 Apache Tika提取數(shù)據(jù)的具體操作
Apache Tika?是一個(gè)功能強(qiáng)大的內(nèi)容分析工具,它能夠從多種文件格式中提取文本、元數(shù)據(jù)以及其他結(jié)構(gòu)化信息,本文介紹了SpringBoot整合ApacheTika進(jìn)行文件內(nèi)容提取和數(shù)據(jù)泄露防護(hù)的實(shí)現(xiàn),感興趣的朋友一起看看吧2025-02-02
Spring?Boot?配置文件類型properties?格式與yml?格式
這篇文章主要介紹了Spring?Boot?配置文件類型properties?格式與yml?格式,文章圍繞主題展開(kāi)詳細(xì)內(nèi)容,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-05-05
基于SpringBoot創(chuàng)建Web頁(yè)面并熱更新的操作步驟
SpringBoot是一個(gè)用于快速開(kāi)發(fā)單個(gè)微服務(wù)的框架,它基于 Spring 框架,簡(jiǎn)化了Spring應(yīng)用的初始化過(guò)程和開(kāi)發(fā)流程,本文給大家介紹了如何基于SpringBoot創(chuàng)建Web頁(yè)面并熱更新,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11
如何通過(guò)Java代碼實(shí)現(xiàn)KMP算法
這篇文章主要介紹了如何通過(guò)Java代碼實(shí)現(xiàn)KMP算法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
Java RabbitMQ 中的消息長(zhǎng)期不消費(fèi)會(huì)過(guò)期嗎
RabbitMQ支持消息的過(guò)期時(shí)間,在消息發(fā)送時(shí)可以進(jìn)行指定。 RabbitMQ支持隊(duì)列的過(guò)期時(shí)間,從消息入隊(duì)列開(kāi)始計(jì)算,只要超過(guò)了隊(duì)列的超時(shí)時(shí)間配置,那么消息會(huì)自動(dòng)的清除2021-09-09

