Spring構(gòu)造器注入及@Autowired、lombok的@RequiredArgsConstructor的異同點說明
一、構(gòu)造器注入:Spring 是如何 “找到參數(shù)” 的?
構(gòu)造器SysUserController:
public SysUserController(SysUserService sysUserService,
ApplicationContext applicationContext,
UserAuthService userAuthService) {
this.sysUserService = sysUserService;
this.applicationContext = applicationContext;
this.userAuthService = userAuthService;
}Spring 創(chuàng)建 SysUserController Bean 時,找構(gòu)造器參數(shù)的過程分 3 步,核心是「按類型匹配 + 按名稱兜底」:
步驟 1:掃描并注冊所有 Bean
Spring 啟動時,會掃描標注了 @Service/@Component/@Repository 等注解的類,把它們實例化成 Bean,并存入「Spring 容器」(可以理解為一個 “Bean 字典”):
SysUserService:因為加了@Service,會被注冊為「類型 = SysUserService,名稱 = sysUserService」的 Bean;ApplicationContext:Spring 內(nèi)置的核心 Bean,啟動時自動注冊,類型就是ApplicationContext;UserAuthService:它的實現(xiàn)類(比如UserAuthServiceImpl)加了@Service,會被注冊為「類型 = UserAuthService(接口),名稱 = userAuthServiceImpl」的 Bean。
步驟 2:解析構(gòu)造器參數(shù),按「類型」匹配 Bean
Spring 發(fā)現(xiàn) SysUserController 有且只有一個有參構(gòu)造器,會:
遍歷構(gòu)造器的每個參數(shù),先按「參數(shù)類型」去容器里找匹配的 Bean:
- 第一個參數(shù)
SysUserService:容器里有且只有一個SysUserService類型的 Bean → 直接匹配; - 第二個參數(shù)
ApplicationContext:容器里只有一個 Spring 內(nèi)置的ApplicationContextBean → 直接匹配; - 第三個參數(shù)
UserAuthService:容器里有UserAuthService類型的實現(xiàn)類 Bean → 直接匹配。
如果一個類型對應多個 Bean(比如有兩個 UserAuthService 實現(xiàn)類),Spring 會再按「參數(shù)名稱」匹配(比如參數(shù)名是 userAuthService,就找名稱為 userAuthService 的 Bean);
把匹配到的 Bean 作為參數(shù),調(diào)用構(gòu)造器,創(chuàng)建 SysUserController 實例。
步驟 3:特殊情況處理(兜底邏輯)
- 如果按類型 / 名稱都找不到參數(shù)對應的 Bean → 拋出
NoSuchBeanDefinitionException(Bean 不存在); - 如果一個類型有多個 Bean,且參數(shù)名稱也匹配不上 → 拋出
NoUniqueBeanDefinitionException(Bean 不唯一);
解決方法:用 @Qualifier("bean名稱") 標注參數(shù),指定具體要哪個 Bean,比如:
運行
public SysUserController(SysUserService sysUserService,
ApplicationContext applicationContext,
@Qualifier("userAuthServiceImpl") UserAuthService userAuthService) {
// ...
}二、構(gòu)造器注入 vs @Autowired:核心本質(zhì)差異
先給結(jié)論:兩者最終都是從 Spring 容器找 Bean,但注入時機、強制約束、安全性完全不同,用表格對比核心差異:
表格
| 維度 | 構(gòu)造器注入 | @Autowired 字段注入 |
|---|---|---|
| 注入時機 | Bean 實例化時(構(gòu)造階段) | Bean 實例化后(初始化階段) |
| 依賴強制性 | 必須找到所有參數(shù) Bean,否則實例化失敗 | 可選(默認 required=true,可設(shè) false) |
| 字段不可變性 | 可加 final,實例化后字段不可修改 | 不能加 final,字段可被篡改 |
| 空指針風險 | 無(實例化時就注入,字段必不為 null) | 有(初始化失敗時字段為 null) |
| 依賴透明度 | 所有依賴都在構(gòu)造器參數(shù)里,一目了然 | 依賴藏在字段里,需看注解才知道 |
| 循環(huán)依賴處理 | 無法解決循環(huán)依賴(Spring 不支持) | 可以解決循環(huán)依賴(Spring 緩存半成品) |
| 單元測試友好性 | 直接傳參創(chuàng)建實例,無需 Spring 容器 | 需 Mock 容器或反射注入,繁瑣 |
關(guān)鍵差異拆解(通俗版)
1. 注入時機:“先有雞還是先有蛋”
- 構(gòu)造器注入:創(chuàng)建
SysUserController實例時,必須先拿到所有參數(shù) Bean → 實例化完成后,所有依賴都已就緒; - @Autowired 字段注入:先調(diào)用無參構(gòu)造器創(chuàng)建
SysUserController空實例 → 再通過反射給字段賦值 → 如果賦值失敗,實例已經(jīng)創(chuàng)建但字段是 null。
舉個例子:如果 SysUserService Bean 不存在:
- 構(gòu)造器注入:Spring 連
SysUserController實例都創(chuàng)建不出來,啟動直接報錯,提前暴露問題; @Autowired注入:SysUserController實例能創(chuàng)建,但sysUserService字段是 null → 運行時調(diào)用sysUserService才會報空指針,問題暴露晚。
2. 不可變性:“常量 vs 變量”
- 構(gòu)造器注入的字段可以加
final,一旦賦值就不能改 → 線程安全(多線程調(diào)用 Controller 時,字段不會被篡改); @Autowired字段不能加final(加了會編譯報錯)→ 字段可能被反射 / 代碼篡改,有線程安全風險。
3. 循環(huán)依賴:“死循環(huán) vs 緩兵之計”
- 構(gòu)造器注入:如果 A 的構(gòu)造器依賴 B,B 的構(gòu)造器又依賴 A → Spring 實例化時會陷入死循環(huán),直接報錯(強制你解耦,是好事);
@Autowired字段注入:Spring 會創(chuàng)建 “半成品 Bean”(實例化但未初始化),暫時緩存 → 先給 A 注入 B 的半成品,再給 B 注入 A 的半成品 → 表面解決循環(huán)依賴,但會埋下運行時隱患。
三、為什么 Spring 官方推薦構(gòu)造器注入?
結(jié)合你的代碼場景,推薦構(gòu)造器注入的核心原因:
- 提前暴露問題:如果
SysUserService/UserAuthService沒被注冊為 Bean,啟動時就報錯,而不是運行時才發(fā)現(xiàn); - 依賴透明:看構(gòu)造器就知道
SysUserController依賴哪些 Bean,不用翻字段注解; - 線程安全:
final字段不可變,Controller 是單例 Bean,多線程調(diào)用時不會出問題; - 測試方便:單元測試時,不用啟動 Spring 容器,直接
new SysUserController(mockSysUserService, mockContext, mockUserAuthService)就能創(chuàng)建實例,無需 Mock 注解。
總結(jié):
構(gòu)造器注入找參數(shù)的邏輯:Spring 先掃描注冊所有 Bean → 按參數(shù)「類型」匹配 Bean → 類型不唯一時按「名稱」匹配 → 匹配失敗則啟動報錯;
和 @Autowired 的本質(zhì)差異:核心是「注入時機」和「依賴強制性」—— 構(gòu)造器注入是 “實例化時必須有依賴”,@Autowired 是 “實例化后可選注入”;
實戰(zhàn)建議:
- 必需依賴(比如你的
SysUserService/UserAuthService)→ 用構(gòu)造器注入 +final; - 可選依賴(非必須的 Bean)→ 用
@Autowired(required = false); - 杜絕 “構(gòu)造器 + @Autowired” 混用,避免依賴不透明和初始化風險。
你的這段構(gòu)造器注入代碼是完全符合 Spring 最佳實踐的,這也是為什么我們之前要把 SysUserService 加到構(gòu)造器里,而不是保留 @Autowired 的原因。
Spring 注入規(guī)范的核心細節(jié),我會結(jié)合你的代碼場景,用 “原理 + 實戰(zhàn)” 的方式講清楚,讓你不僅知其然,還知其所以然。
四、final在構(gòu)造器注入中「不是必須加,但強烈推薦加」
1.final的核心作用(不是為了 Spring,是為了你的代碼)
final 修飾字段的含義是:字段一旦賦值,就不能被修改。在構(gòu)造器注入中加 final 的核心價值和 Spring 注入邏輯無關(guān),而是為了代碼的「安全性 + 可讀性」:
- 線程安全:Controller 是 Spring 單例 Bean(整個應用只有一個實例),多線程調(diào)用時,
final字段不會被篡改(比如不會被反射 / 其他代碼意外賦值為 null); - 依賴不可變:明確表示這些依賴是 Controller 運行的 “必需品”,實例化后絕不更改;
- 編譯期校驗:如果構(gòu)造器沒有給
final字段賦值,編譯器會直接報錯(比如漏寫this.sysUserService = sysUserService),提前規(guī)避低級錯誤。
2. 不加final會怎么樣?
如果去掉 final:
private SysUserService sysUserService; // 去掉final
private ApplicationContext applicationContext;
private UserAuthService userAuthService;
public SysUserController(SysUserService sysUserService, ApplicationContext applicationContext, UserAuthService userAuthService) {
this.sysUserService = sysUserService;
this.applicationContext = applicationContext;
this.userAuthService = userAuthService;
}- Spring 依然能正常注入(構(gòu)造器注入的核心是 “構(gòu)造器傳參”,和
final無關(guān)); - 但字段變成 “可變的”,存在被篡改的風險(比如在某個方法里寫
this.sysUserService = null); - 代碼可讀性下降:無法一眼看出這些依賴是 “必需且不可變” 的。
3. 總結(jié):final的使用原則
表格
| 場景 | 是否加 final | 原因 |
|---|---|---|
| 構(gòu)造器注入的「必需依賴」 | 必須加 | 線程安全 + 依賴不可變 + 編譯校驗 |
| 構(gòu)造器注入的「可選依賴」 | 不加 | (實際很少見,可選依賴建議用 @Autowired (required=false)) |
你的代碼中 SysUserService/ApplicationContext/UserAuthService 都是 Controller 的必需依賴,加 final 是完全符合最佳實踐的。
五、為什么你的代碼不用@RequiredArgsConstructor?
1. 先搞懂@RequiredArgsConstructor是什么
@RequiredArgsConstructor 是 Lombok 提供的注解,作用是:自動為類中所有 final 字段生成對應的構(gòu)造器。
@RequiredArgsConstructor // 加這個注解
@RestController
@RequestMapping("/user")
public class SysUserController {
private final SysUserService sysUserService;
private final ApplicationContext applicationContext;
private final UserAuthService userAuthService;
// 不用手動寫構(gòu)造器,Lombok 會自動生成下面的代碼:
// public SysUserController(SysUserService sysUserService, ApplicationContext applicationContext, UserAuthService userAuthService) {
// this.sysUserService = sysUserService;
// this.applicationContext = applicationContext;
// this.userAuthService = userAuthService;
// }
}2. 你的代碼不用@RequiredArgsConstructor的核心原因
不是 “不能用”,而是 “手動寫構(gòu)造器更清晰”,具體分兩種情況:
- 情況 1:你想「顯式控制構(gòu)造器」手動寫構(gòu)造器可以清晰看到所有依賴,尤其是團隊協(xié)作時,新人不用去查 Lombok 注解的作用,直接看構(gòu)造器就知道依賴了哪些 Bean;
- 情況 2:不需要 Lombok 簡化(字段少)你的構(gòu)造器只有 3 個參數(shù),手動寫的代碼量很少,沒必要用 Lombok 注解;如果構(gòu)造器參數(shù)有 5+ 個,用
@RequiredArgsConstructor能減少重復代碼。
3. 手動構(gòu)造器 vs@RequiredArgsConstructor的對比
| 維度 | 手動寫構(gòu)造器 | @RequiredArgsConstructor |
|---|---|---|
| 代碼可讀性 | 高(直接看構(gòu)造器) | 中(需知道注解作用) |
| 代碼量 | 稍多(3 行賦值) | 極少(1 行注解) |
| 編譯期依賴 | 無(純 Java 語法) | 需 Lombok 插件 / 依賴 |
| 調(diào)試友好性 | 高(斷點可直接打到構(gòu)造器) | 中(需 Lombok 反編譯看生成的代碼) |
4. 補充:如果要用@RequiredArgsConstructor,代碼怎么寫?
你的代碼可以改成這樣(完全等價,且更簡潔):
@Slf4j
@RequiredArgsConstructor // 替代手動構(gòu)造器
@RestController
@RequestMapping("/user")
public class SysUserController {
private final SysUserService sysUserService;
private final ApplicationContext applicationContext;
private final UserAuthService userAuthService;
// 無需手動寫構(gòu)造器,Lombok 自動生成
// ... login 方法等邏輯不變
}總結(jié)
關(guān)于 final:
- 構(gòu)造器注入中
final不是 Spring 強制要求的,但「必需依賴」強烈推薦加; - 核心價值:線程安全、依賴不可變、編譯期校驗,和構(gòu)造器注入的 “找參數(shù)邏輯” 無關(guān)。
關(guān)于 @RequiredArgsConstructor:
- 你的代碼不用這個注解,是因為「手動寫構(gòu)造器更清晰」,而非技術(shù)上不可用;
- 如果想簡化代碼,加
@RequiredArgsConstructor完全可行,和手動構(gòu)造器效果一致。
你的這段代碼寫法(final 字段 + 手動構(gòu)造器注入)是 Spring 官方最推薦的 “標準寫法”,既保證了代碼的安全性,又兼顧了可讀性,完全無需調(diào)整。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringMVC轉(zhuǎn)發(fā)與重定向參數(shù)傳遞的實現(xiàn)詳解
這篇文章主要介紹了SpringMVC轉(zhuǎn)發(fā)與重定向參數(shù)傳遞,對于重定向,可以通過FlashMap或RedirectAttributes來在請求間傳遞數(shù)據(jù),因為重定向涉及兩個獨立的HTTP請求,而轉(zhuǎn)發(fā)則在同一請求內(nèi)進行,數(shù)據(jù)可以直接通過HttpServletRequest共享,需要的朋友可以參考下2022-07-07
SpringBoot實現(xiàn)企業(yè)級敏感詞攔截檢查系統(tǒng)的設(shè)計方案
本文介紹了基于SpringBoot和DFA算法的敏感詞檢測系統(tǒng)設(shè)計,涵蓋業(yè)務背景、技術(shù)架構(gòu)、核心模塊、數(shù)據(jù)庫設(shè)計、使用示例、監(jiān)控與告警、安全設(shè)計、部署方案等多個方面,旨在實現(xiàn)高效的實時敏感詞檢測,保證內(nèi)容合規(guī)性,提升平臺穩(wěn)定性,需要的朋友可以參考下2026-01-01
Java源碼解析之HashMap的put、resize方法詳解
這篇文章主要介紹了Java源碼解析之HashMap的put、resize方法詳解,文中有非常詳細的代碼示例,對正在學習java的小伙伴們有很大的幫助,需要的朋友可以參考下2021-04-04

