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

