SpringBoot中依賴注入的常用方式對比詳解
依賴注入(Dependency Injection,DI)是 Spring 框架的核心特性之一,也是實(shí)現(xiàn) IoC(控制反轉(zhuǎn))的具體方式。Spring 提供了多種依賴注入方式,每種方式各有優(yōu)劣。本文系統(tǒng)講解構(gòu)造器注入、字段注入、Setter 注入、方法注入的用法與區(qū)別,并涵蓋 @Autowired 與 @Resource 的差異、循環(huán)依賴問題、最佳實(shí)踐等內(nèi)容。
一、依賴注入的基本概念
1.1 什么是依賴注入
依賴注入是指對象不再自行創(chuàng)建其依賴項(xiàng),而是由容器(Spring IoC 容器)在運(yùn)行時(shí)將依賴項(xiàng)注入到對象中。
傳統(tǒng)方式(手動創(chuàng)建依賴):Controller ──new──→ Service ──new──→ Repository,對象自己控制依賴的創(chuàng)建,耦合度高
依賴注入(容器管理依賴):
- Container ──注入──→ Controller
- Container ──注入──→ Service
- Container ──注入──→ Repository
容器統(tǒng)一管理對象的創(chuàng)建和裝配,對象只聲明需要的依賴
1.2 依賴注入的三個(gè)角色
| 角色 | 說明 | 示例 |
|---|---|---|
| 依賴者 | 需要其他組件的對象 | UserController |
| 依賴項(xiàng) | 被需要的組件 | UserService |
| 注入者 | 負(fù)責(zé)將依賴項(xiàng)注入依賴者 | Spring IoC 容器 |
1.3 Spring 依賴注入方式總覽
| 注入方式 | 注解 / 配置 | 特點(diǎn) |
|---|---|---|
| 構(gòu)造器注入 | 構(gòu)造方法 + @Autowired(可省略) | 官方推薦,不可變,可保證完全初始化 |
| 字段注入 | @Autowired 標(biāo)注在字段上 | 簡潔,但不推薦 |
| Setter 注入 | @Autowired 標(biāo)注在 Setter 方法上 | 可選依賴,可重新注入 |
| 方法注入 | @Autowired 標(biāo)注在普通方法上 | 靈活,可注入多個(gè)依賴 |
| Lookup 方法注入 | @Lookup | 解決單例注入原型的問題 |
二、構(gòu)造器注入
2.1 基本用法
通過構(gòu)造方法將依賴傳入類中。Spring 4.3+ 如果類只有一個(gè)構(gòu)造方法,@Autowired 可以省略。
import org.springframework.stereotype.Service;
/**
* 訂單服務(wù)
*/
@Service
public class OrderService {
private final UserService userService;
private final OrderRepository orderRepository;
/**
* 構(gòu)造器注入(Spring 4.3+ 單構(gòu)造方法可省略 @Autowired)
*/
public OrderService(UserService userService, OrderRepository orderRepository) {
this.userService = userService;
this.orderRepository = orderRepository;
}
public void createOrder(Long userId) {
// 使用注入的依賴
userService.getUser(userId);
orderRepository.save(null);
}
}
2.2 結(jié)合 Lombok 簡化
實(shí)際項(xiàng)目中最常見的寫法,使用 @RequiredArgsConstructor 自動生成構(gòu)造方法:
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 訂單服務(wù)
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
private final UserService userService; // final 字段
private final OrderRepository orderRepository; // final 字段
public void createOrder(Long userId) {
userService.getUser(userId);
orderRepository.save(null);
}
}
關(guān)鍵:@RequiredArgsConstructor 只為 final 字段和 @NonNull 標(biāo)注的字段生成構(gòu)造方法。非 final 字段不會包含在構(gòu)造方法中。
2.3 多構(gòu)造方法場景
當(dāng)類有多個(gè)構(gòu)造方法時(shí),必須用 @Autowired 指定主構(gòu)造方法:
@Service
public class OrderService {
private final UserService userService;
private final OrderRepository orderRepository;
private final LogService logService; // 可選依賴
/**
* 主構(gòu)造方法(Spring 使用此構(gòu)造方法實(shí)例化)
*/
@Autowired
public OrderService(UserService userService, OrderRepository orderRepository) {
this.userService = userService;
this.orderRepository = orderRepository;
this.logService = null;
}
/**
* 輔助構(gòu)造方法(不參與 Spring 注入)
*/
public OrderService(UserService userService, OrderRepository orderRepository,
LogService logService) {
this.userService = userService;
this.orderRepository = orderRepository;
this.logService = logService;
}
}
2.4 優(yōu)缺點(diǎn)
| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
依賴不可變(final 字段),線程安全 | 依賴較多時(shí)構(gòu)造方法參數(shù)過多 |
| 對象創(chuàng)建即完全初始化,不會出現(xiàn) NPE | 無法注入循環(huán)依賴(但這本身是好事) |
| 易于單元測試(可直接 new 傳入 mock) | 需要配合 Lombok 減少樣板代碼 |
| 明確聲明所有必需依賴 | |
| Spring 官方推薦 |
三、字段注入
3.1 基本用法
直接在字段上標(biāo)注 @Autowired,Spring 通過反射注入依賴:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 訂單服務(wù)(字段注入 - 不推薦)
*/
@Service
public class OrderService {
@Autowired
private UserService userService;
@Autowired
private OrderRepository orderRepository;
public void createOrder(Long userId) {
userService.getUser(userId);
orderRepository.save(null);
}
}
3.2 優(yōu)缺點(diǎn)
| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
| 代碼簡潔,最少樣板代碼 | 無法使用 final,依賴可變,不安全 |
| 不需要構(gòu)造方法或 Setter | 單元測試?yán)щy(無法通過構(gòu)造方法傳入 mock) |
| 隱藏了依賴關(guān)系(類表面看不到依賴) | |
| 違反單一職責(zé)(加依賴太容易,容易過度膨脹) | |
| 依賴注入發(fā)生在對象創(chuàng)建之后,可能存在半初始化狀態(tài) | |
| Spring 官方不推薦 |
Spring 官方建議:從 Spring 4.0 開始,官方文檔明確推薦構(gòu)造器注入,不推薦字段注入。
3.3 為什么字段注入仍被廣泛使用
盡管不推薦,字段注入在實(shí)際項(xiàng)目中仍很常見,原因:
- 代碼量少,開發(fā)效率高
- 早期 Spring 教程普遍使用字段注入
- 依賴較少時(shí),缺點(diǎn)不明顯
但這是技術(shù)債務(wù),建議在新項(xiàng)目中避免使用。
四、Setter 注入
4.1 基本用法
通過 Setter 方法注入依賴:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 訂單服務(wù)(Setter 注入)
*/
@Service
public class OrderService {
private UserService userService;
private OrderRepository orderRepository;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@Autowired
public void setOrderRepository(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public void createOrder(Long userId) {
userService.getUser(userId);
orderRepository.save(null);
}
}
4.2 可選依賴
通過 required = false 標(biāo)記可選依賴,當(dāng)容器中找不到對應(yīng) Bean 時(shí)不報(bào)錯(cuò):
@Autowired(required = false)
public void setLogService(LogService logService) {
this.logService = logService;
}
注意:構(gòu)造器注入不支持 required = false。如果需要可選依賴,必須使用 Setter 注入。
4.3 優(yōu)缺點(diǎn)
| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
支持可選依賴(required = false) | 依賴可變(非 final),不保證不變性 |
| 可以在對象創(chuàng)建后重新注入(熱替換) | 對象可能處于未完全初始化狀態(tài) |
| 單元測試方便(可調(diào)用 Setter 傳入 mock) | 代碼量比字段注入多 |
| 解決循環(huán)依賴(與構(gòu)造器注入配合) |
五、方法注入
5.1 基本用法
在任意方法上標(biāo)注 @Autowired,可以一次注入多個(gè)依賴:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 訂單服務(wù)(方法注入)
*/
@Service
public class OrderService {
private UserService userService;
private OrderRepository orderRepository;
@Autowired
public void configureDependencies(UserService userService, OrderRepository orderRepository) {
this.userService = userService;
this.orderRepository = orderRepository;
}
public void createOrder(Long userId) {
userService.getUser(userId);
orderRepository.save(null);
}
}
5.2 優(yōu)缺點(diǎn)
| 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|
| 靈活,可一次注入多個(gè)依賴 | 不如構(gòu)造器注入清晰 |
| 方法名可自定義,語義更靈活 | 使用場景少 |
| 可以在方法中添加初始化邏輯 |
使用場景:較少使用,通常構(gòu)造器注入或 Setter 注入已足夠。適合需要同時(shí)設(shè)置多個(gè)相關(guān)依賴并執(zhí)行初始化邏輯的場景。
六、@Autowired 與 @Resource 的區(qū)別
@Autowired 和 @Resource 都用于依賴注入,但來源和行為不同。
6.1 基本對比
| 特性 | @Autowired | @Resource |
|---|---|---|
| 來源 | Spring(org.springframework.beans.factory.annotation) | JSR-250 標(biāo)準(zhǔn)(jakarta.annotation) |
| 默認(rèn)匹配方式 | 按類型(byType) | 按名稱(byName) |
| 找不到時(shí)的行為 | required = false 可設(shè)為可選 | 無法設(shè)為可選,找不到直接報(bào)錯(cuò) |
| 配合注解 | @Qualifier 指定 Bean 名稱 | name 屬性直接指定 |
| 適用位置 | 構(gòu)造器、字段、Setter、方法 | 字段、Setter(不支持構(gòu)造器) |
| 框架依賴 | 僅 Spring | 標(biāo)準(zhǔn) JSR-250,其他框架也支持 |
6.2 @Autowired 匹配流程
@Autowired private UserService userService;
匹配流程:
1. 按類型查找 UserService 的 Bean
2. 找到唯一 → 注入 ?
3. 找到多個(gè) → 按字段名 "userService" 匹配 Bean 名稱
4. 仍不唯一 → 報(bào)錯(cuò) NoUniqueBeanDefinitionException
5. 配合 @Qualifier("beanName") → 按指定名稱注入
示例:
// 多個(gè)實(shí)現(xiàn)類場景
public interface PaymentService {
void pay(String orderId);
}
@Service("alipayService")
public class AlipayServiceImpl implements PaymentService {
@Override
public void pay(String orderId) {
System.out.println("支付寶支付: " + orderId);
}
}
@Service("wechatPayService")
public class WechatPayServiceImpl implements PaymentService {
@Override
public void pay(String orderId) {
System.out.println("微信支付: " + orderId);
}
}
多實(shí)現(xiàn)場景的注入方式:
// 方式1:@Autowired + @Qualifier(按名稱指定)
@Autowired
@Qualifier("alipayService")
private PaymentService paymentService;
// 方式2:字段名與 Bean 名稱一致(Spring 自動匹配)
@Autowired
private PaymentService alipayService; // 字段名 "alipayService" 匹配 Bean 名稱
// 方式3:@Primary 標(biāo)注默認(rèn)實(shí)現(xiàn)
@Service
@Primary // 多個(gè)實(shí)現(xiàn)時(shí),@Autowired 優(yōu)先注入標(biāo)注了 @Primary 的
public class AlipayServiceImpl implements PaymentService { }
6.3 @Resource 匹配流程
@Resource private UserService userService;
匹配流程:
1. 如果指定了 name → 只按名稱查找
2. 如果未指定 name → 先按字段名 "userService" 查找
3. 找不到 → 再按類型查找
4. 仍找不到 → 報(bào)錯(cuò) NoSuchBeanDefinitionException
示例:
// 方式1:默認(rèn)按字段名匹配 @Resource private PaymentService alipayService; // 按名稱 "alipayService" 查找 // 方式2:指定 name @Resource(name = "wechatPayService") private PaymentService paymentService; // 方式3:指定 type @Resource(type = AlipayServiceImpl.class) private PaymentService paymentService;
6.4 選型建議
| 場景 | 推薦 |
|---|---|
| Spring 項(xiàng)目,構(gòu)造器注入 | @Autowired(@Resource 不支持構(gòu)造器) |
| 需要按名稱注入 | @Autowired + @Qualifier |
| 跨框架兼容 | @Resource(JSR-250 標(biāo)準(zhǔn)) |
| 簡單項(xiàng)目 | 均可,團(tuán)隊(duì)統(tǒng)一即可 |
Spring 官方推薦:優(yōu)先使用 @Autowired,構(gòu)造器注入時(shí) @Autowired 可省略。
七、@Autowired 的 required 屬性
7.1 必需依賴 vs 可選依賴
// 必需依賴(默認(rèn) required = true) // 容器中找不到對應(yīng) Bean 會啟動失敗 @Autowired private UserService userService; // 可選依賴(required = false) // 容器中找不到對應(yīng) Bean 不報(bào)錯(cuò),注入 null @Autowired(required = false) private LogService logService;
7.2 各注入方式對 required 的支持
| 注入方式 | 支持 required = false | 說明 |
|---|---|---|
| 構(gòu)造器注入 | 不支持 | 構(gòu)造方法參數(shù)始終是必需的 |
| 字段注入 | 支持 | @Autowired(required = false) |
| Setter 注入 | 支持 | @Autowired(required = false) |
| 方法注入 | 支持 | @Autowired(required = false) |
7.3 Optional 替代方案
Java 8+ 推薦用 Optional 聲明可選依賴,語義更清晰:
import java.util.Optional;
@Service
public class OrderService {
private final UserService userService;
private final Optional<LogService> logService;
public OrderService(UserService userService, Optional<LogService> logService) {
this.userService = userService;
this.logService = logService;
}
public void createOrder(Long userId) {
userService.getUser(userId);
// 使用前判斷是否存在
logService.ifPresent(service -> service.log("創(chuàng)建訂單: " + userId));
}
}
注意:Optional 只能用于構(gòu)造器注入和 Setter 注入,不能用于字段注入。
八、循環(huán)依賴問題
8.1 什么是循環(huán)依賴
兩個(gè)或多個(gè) Bean 互相依賴,形成閉環(huán):
A 依賴 B,B 依賴 A → 循環(huán)依賴
@Service
class A {
@Autowired
B b; // A 需要 B
}
@Service
class B {
@Autowired
A a; // B 需要 A
}
8.2 Spring 如何解決循環(huán)依賴
Spring 通過三級緩存機(jī)制解決 Setter/字段注入的循環(huán)依賴:
| 緩存級別 | 名稱 | 存儲內(nèi)容 |
|---|---|---|
| 一級緩存 | singletonObjects | 完全初始化好的 Bean(成品) |
| 二級緩存 | earlySingletonObjects | 提前暴露的半成品 Bean(已實(shí)例化,未注入依賴) |
| 三級緩存 | singletonFactories | Bean 工廠(ObjectFactory,用于生成半成品) |
解決循環(huán)依賴的流程(A 依賴 B,B 依賴 A):
1. 創(chuàng)建 A:實(shí)例化 A → 將 A 的工廠放入三級緩存
2. 注入 A 的依賴 B → 創(chuàng)建 B
3. 創(chuàng)建 B:實(shí)例化 B → 將 B 的工廠放入三級緩存
4. 注入 B 的依賴 A → 查找 A
→ 一級緩存沒有
→ 二級緩存沒有
→ 三級緩存有 A 的工廠 → 生成 A 的半成品 → 放入二級緩存 → 刪除三級緩存
5. B 拿到 A 的半成品 → B 注入完成 → B 放入一級緩存
6. A 拿到 B → A 注入完成 → A 放入一級緩存
8.3 構(gòu)造器注入的循環(huán)依賴
構(gòu)造器注入的循環(huán)依賴無法解決!
- A 的構(gòu)造方法需要 B → 創(chuàng)建 B
- B 的構(gòu)造方法需要 A → 創(chuàng)建 A
- A 的構(gòu)造方法需要 B → ... 死循環(huán)
Spring 會拋出:BeanCurrentlyInCreationException
8.4 各注入方式的循環(huán)依賴支持
| 注入方式 | 是否支持循環(huán)依賴 | 說明 |
|---|---|---|
| 構(gòu)造器注入 | ? 不支持 | 無法實(shí)例化任何一方,直接報(bào)錯(cuò) |
| 字段注入 | ? 支持 | 通過三級緩存解決 |
| Setter 注入 | ? 支持 | 通過三級緩存解決 |
| 方法注入 | ? 支持 | 通過三級緩存解決 |
關(guān)鍵理解:構(gòu)造器注入不支持循環(huán)依賴,這其實(shí)是優(yōu)點(diǎn)而非缺點(diǎn)。循環(huán)依賴通常意味著設(shè)計(jì)有問題,構(gòu)造器注入強(qiáng)制你面對并解決它。
8.5 循環(huán)依賴的解決方案
| 方案 | 說明 | 適用場景 |
|---|---|---|
| 重構(gòu)消除循環(huán) | 提取公共邏輯到第三個(gè)類 | 首選方案 |
| 改為 Setter 注入 | 將其中一方改為 Setter 注入 | 臨時(shí)方案,不推薦長期保留 |
| 使用 @Lazy | 延遲注入代理對象 | 臨時(shí)方案 |
| 事件機(jī)制解耦 | 用事件代替直接調(diào)用 | 適合通知類場景 |
@Lazy 方案:
@Service
public class OrderService {
private final UserService userService;
// @Lazy 注入代理對象,首次使用時(shí)才真正初始化
public OrderService(@Lazy UserService userService) {
this.userService = userService;
}
}
九、Lookup 方法注入
9.1 問題場景
單例 Bean 注入原型 Bean 時(shí),原型 Bean 只在注入時(shí)創(chuàng)建一次,之后不再創(chuàng)建新的:
@Service
public class OrderService {
// 希望每次使用都是新的實(shí)例,但實(shí)際只創(chuàng)建了一次
@Autowired
private PrototypeBean prototypeBean; // 始終是同一個(gè)對象 ?
}
@Scope("prototype")
@Component
public class PrototypeBean {
// 原型作用域,應(yīng)該每次獲取都創(chuàng)建新實(shí)例
}
9.2 解決方案:@Lookup
import org.springframework.beans.factory.annotation.Lookup;
import org.springframework.stereotype.Service;
/**
* 訂單服務(wù)
*/
@Service
public abstract class OrderService {
/**
* Lookup 方法注入:每次調(diào)用都返回新的 PrototypeBean 實(shí)例
* Spring 通過 CGLIB 動態(tài)子類化重寫此方法
*/
@Lookup
protected abstract PrototypeBean getPrototypeBean();
public void processOrder(String orderId) {
// 每次調(diào)用 getPrototypeBean() 都返回新實(shí)例
PrototypeBean bean = getPrototypeBean();
bean.doSomething(orderId);
}
}
注意:@Lookup 標(biāo)注的方法必須是抽象方法(或返回值與 Bean 類型匹配的具體方法),Spring 通過 CGLIB 生成子類重寫該方法。因此類不能是 final,方法也不能是 final/private。
9.3 替代方案:ObjectFactory
如果不想用抽象類,可以用 ObjectFactory:
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final ObjectProvider<PrototypeBean> prototypeBeanProvider;
public OrderService(ObjectProvider<PrototypeBean> prototypeBeanProvider) {
this.prototypeBeanProvider = prototypeBeanProvider;
}
public void processOrder(String orderId) {
// 每次調(diào)用 getObject() 都返回新實(shí)例
PrototypeBean bean = prototypeBeanProvider.getObject();
bean.doSomething(orderId);
}
}
優(yōu)勢:ObjectProvider 是構(gòu)造器注入友好的方案,不需要抽象類,推薦使用。
十、泛型注入
Spring 支持泛型類型的依賴注入,通過類型參數(shù)自動匹配對應(yīng)實(shí)現(xiàn)。
10.1 泛型基類
/**
* 通用 Repository 基類
* @param <T> 實(shí)體類型
*/
public abstract class BaseRepository<T> {
public abstract T findById(Long id);
public abstract void save(T entity);
}
10.2 具體實(shí)現(xiàn)
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository extends BaseRepository<User> {
@Override
public User findById(Long id) {
return new User(id, "張三");
}
@Override
public void save(User entity) {
System.out.println("保存用戶: " + entity);
}
}
@Repository
public class OrderRepository extends BaseRepository<Order> {
@Override
public Order findById(Long id) {
return new Order(id, 100.0);
}
@Override
public void save(Order entity) {
System.out.println("保存訂單: " + entity);
}
}
10.3 泛型 Service 注入
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 通用 Service 基類
* Spring 根據(jù)泛型參數(shù)自動匹配對應(yīng)的 Repository
*/
public abstract class BaseService<T> {
protected BaseRepository<T> repository;
@Autowired
public void setRepository(BaseRepository<T> repository) {
this.repository = repository;
}
public T getById(Long id) {
return repository.findById(id);
}
public void save(T entity) {
repository.save(entity);
}
}
@Service
public class UserService extends BaseService<User> {
// Spring 自動注入 UserRepository(因?yàn)?BaseRepository<User> 匹配 UserRepository)
}
@Service
public class OrderService extends BaseService<Order> {
// Spring 自動注入 OrderRepository(因?yàn)?BaseRepository<Order> 匹配 OrderRepository)
}
使用:
@Autowired private UserService userService; userService.getById(1L); // 調(diào)用 UserRepository.findById() userService.save(new User()); // 調(diào)用 UserRepository.save()
優(yōu)勢:泛型注入實(shí)現(xiàn)了代碼復(fù)用,避免了為每個(gè)實(shí)體重復(fù)編寫 CRUD 方法。
十一、Bean 生命周期回調(diào)與注入
11.1 注入時(shí)機(jī)
Bean 生命周期中的注入時(shí)機(jī):
1. 實(shí)例化(Instantiation) → 構(gòu)造方法調(diào)用
2. 屬性填充(Population) → 依賴注入發(fā)生在此階段
3. 初始化(Initialization)
├── @PostConstruct → 注入完成后執(zhí)行
├── InitializingBean.afterPropertiesSet()
└── @Bean(initMethod=...)
4. 使用(In Use)
5. 銷毀(Destruction)
├── @PreDestroy
├── DisposableBean.destroy()
└── @Bean(destroyMethod=...)
11.2 @PostConstruct 中使用注入的依賴
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Service;
/**
* 訂單服務(wù)
*/
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
// ?? 此時(shí)依賴已注入,但如果通過字段注入則可能還未注入
System.out.println("1. 構(gòu)造方法");
}
@PostConstruct
public void init() {
// ? 所有依賴已注入完畢,可以安全使用
System.out.println("2. @PostConstruct: 依賴已就緒");
userService.warmUpCache();
}
@PreDestroy
public void cleanup() {
System.out.println("3. @PreDestroy: 清理資源");
}
}
關(guān)鍵:@PostConstruct 在所有依賴注入完成后執(zhí)行,是執(zhí)行初始化邏輯的安全位置。不要在構(gòu)造方法中調(diào)用被注入的依賴的方法(字段注入場景下依賴可能還未注入)。
十二、四種注入方式橫向?qū)Ρ?/h2>
12.1 全面對比
| 維度 | 構(gòu)造器注入 | 字段注入 | Setter 注入 | 方法注入 |
|---|---|---|---|---|
| 不可變性 | ? final | ? | ? | ? |
| 完全初始化保證 | ? | ? | ? | ? |
| 單元測試便利性 | ? 可直接 new | ? 需反射 | ? 可 Setter | ? 可調(diào)用方法 |
| 可選依賴 | ? | ? | ? | ? |
| 循環(huán)依賴 | ? 不支持 | ? 支持 | ? 支持 | ? 支持 |
| 代碼簡潔度 | ???(Lombok) | ???? | ?? | ?? |
| 依賴可見性 | ? 構(gòu)造方法明確 | ? 隱藏 | ? Setter 明確 | ? 方法明確 |
| Spring 官方推薦 | ? 強(qiáng)烈推薦 | ? 不推薦 | ? 可選依賴時(shí)使用 | ? 特殊場景 |
| 違反單一職責(zé)感知 | ? 參數(shù)過多有警示 | ? 加字段太容易 | ? 中等 | ? 中等 |
12.2 適用場景
| 場景 | 推薦方式 | 理由 |
|---|---|---|
| 必需依賴 | 構(gòu)造器注入 | 保證不可變和完全初始化 |
| 可選依賴 | Setter 注入 | required = false |
| 簡單工具類 | 字段注入(可接受) | 代碼簡潔,但僅限非核心邏輯 |
| 需要多個(gè)相關(guān)依賴同時(shí)設(shè)置 | 方法注入 | 可在方法中添加初始化邏輯 |
| 單例注入原型 | ObjectProvider / @Lookup | 每次獲取新實(shí)例 |
| 通用泛型基類 | 泛型注入 | 代碼復(fù)用 |
配置類 @Bean 方法 | 方法參數(shù)注入 | Spring 自動從容器中查找 |
12.3 @Bean 方法中的注入
在 @Configuration 類中,@Bean 方法的參數(shù)會自動從容器中注入:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
/**
* 參數(shù) userService 自動從容器中注入
* 無需標(biāo)注 @Autowired(Spring 4.3+ 自動注入)
*/
@Bean
public OrderService orderService(UserService userService, OrderRepository orderRepository) {
return new OrderService(userService, orderRepository);
}
}
十三、最佳實(shí)踐
13.1 項(xiàng)目中的注入規(guī)范
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 訂單服務(wù) - 推薦寫法示范
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
// ? 必需依賴:final + 構(gòu)造器注入(Lombok 生成構(gòu)造方法)
private final UserService userService;
private final OrderRepository orderRepository;
// ? 可選依賴:ObjectProvider(構(gòu)造器友好)
private final ObjectProvider<LogService> logServiceProvider;
/**
* 創(chuàng)建訂單
*/
public void createOrder(Long userId, OrderCreateRequest request) {
// 使用必需依賴
User user = userService.getById(userId);
// 使用可選依賴
logServiceProvider.ifAvailable(service ->
service.log("創(chuàng)建訂單: userId=" + userId)
);
// 業(yè)務(wù)邏輯
Order order = new Order();
order.setUserId(userId);
order.setAmount(request.getAmount());
orderRepository.save(order);
}
}
13.2 最佳實(shí)踐清單
| 編號 | 實(shí)踐 | 說明 |
|---|---|---|
| 1 | 構(gòu)造器注入優(yōu)先 | 所有必需依賴用構(gòu)造器注入,配合 @RequiredArgsConstructor |
| 2 | 字段加 final | 保證依賴不可變,線程安全 |
| 3 | 避免字段注入 | 除非是遺留代碼或快速原型 |
| 4 | 可選依賴用 ObjectProvider | 比 @Autowired(required = false) 語義更清晰 |
| 5 | 多實(shí)現(xiàn)用 @Qualifier | 明確指定 Bean 名稱,避免歧義 |
| 6 | 接口編程 | 注入接口類型,而非實(shí)現(xiàn)類 |
| 7 | 避免循環(huán)依賴 | 構(gòu)造器注入天然避免,如遇循環(huán)則重構(gòu) |
| 8 | 不在構(gòu)造方法中調(diào)用依賴方法 | 字段注入場景下依賴可能未就緒 |
| 9 | 初始化邏輯放 @PostConstruct | 此時(shí)所有依賴已注入 |
| 10 | 團(tuán)隊(duì)統(tǒng)一風(fēng)格 | 全部用構(gòu)造器注入 + Lombok,保持一致性 |
13.3 單元測試對比
構(gòu)造器注入的測試便利性:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
/**
* 構(gòu)造器注入 → 單元測試極簡
*/
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
private UserService userService;
@Mock
private OrderRepository orderRepository;
@Test
void testCreateOrder() {
// 直接 new 傳入 mock,無需 Spring 容器
OrderService orderService = new OrderService(userService, orderRepository);
when(userService.getById(1L)).thenReturn(new User(1L, "張三"));
orderService.createOrder(1L, new OrderCreateRequest());
verify(orderRepository, times(1)).save(any());
}
}
字段注入的測試?yán)щy:
/**
* 字段注入 → 單元測試?yán)щy
* 需要反射或 Spring TestContext 才能注入 mock
*/
class OrderServiceFieldInjectionTest {
@Test
void testCreateOrder() throws Exception {
OrderService service = new OrderService();
// 需要通過反射注入私有字段(非常 hacky)
Field field = OrderService.class.getDeclaredField("userService");
field.setAccessible(true);
field.set(service, mock(UserService.class));
// ... 測試邏輯
}
}
十四、總結(jié)
14.1 核心要點(diǎn)速查
- 構(gòu)造器注入是官方推薦:不可變、完全初始化、測試友好
- 字段注入不推薦:隱藏依賴、不可變、測試?yán)щy
- Setter 注入用于可選依賴:
required = false或ObjectProvider @Autowired按類型匹配,配合@Qualifier按名稱指定@Resource按名稱匹配,JSR-250 標(biāo)準(zhǔn),不支持構(gòu)造器- 多實(shí)現(xiàn)場景:
@Primary指定默認(rèn) +@Qualifier精確指定 - 構(gòu)造器注入不支持循環(huán)依賴:這是優(yōu)點(diǎn),強(qiáng)制解決設(shè)計(jì)問題
- Spring 三級緩存解決 Setter/字段注入的循環(huán)依賴
- 單例注入原型用
ObjectProvider或@Lookup - 泛型注入實(shí)現(xiàn)代碼復(fù)用:
BaseRepository<T>自動匹配實(shí)現(xiàn) @PostConstruct是初始化邏輯的安全位置- Lombok
@RequiredArgsConstructor簡化構(gòu)造器注入
14.2 決策樹
依賴是否必需?
├── 必需 → 構(gòu)造器注入(final + @RequiredArgsConstructor)
└── 可選 → ObjectProvider(構(gòu)造器友好)
或 Setter 注入(@Autowired(required = false))
是否有多個(gè)實(shí)現(xiàn)?
├── 是 → @Primary 指定默認(rèn) + @Qualifier 精確指定
└── 否 → 直接注入
是否是單例注入原型?
├── 是 → ObjectProvider 或 @Lookup
└── 否 → 正常注入
是否遇到循環(huán)依賴?
├── 是 → 重構(gòu)消除循環(huán)(首選)
│ 或 @Lazy 延遲注入(臨時(shí))
└── 否 → 正常注入
以上就是SpringBoot中依賴注入的常用方式對比詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot依賴注入的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java后端SSM框架圖片上傳功能實(shí)現(xiàn)方法解析
這篇文章主要介紹了Java后端SSM框架圖片上傳功能實(shí)現(xiàn)方法解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
Spring中@DependsOn注解的使用代碼實(shí)例
這篇文章主要介紹了Spring中@DependsOn注解的使用代碼實(shí)例,Spring中@DependsOn,主要是使用在類和方法上, 作用是當(dāng)前對象要依賴另外一些對象,被依賴的對象會先注冊到Spring的IOC容器中,需要的朋友可以參考下2024-01-01
Spring?多數(shù)據(jù)源方法級別注解實(shí)現(xiàn)過程
多數(shù)據(jù)源管理是Spring框架中非常重要的一部分,它可以提高應(yīng)用程序的靈活性和可靠性,從而更好地滿足業(yè)務(wù)需求,這篇文章主要介紹了Spring?多數(shù)據(jù)源方法級別注解實(shí)現(xiàn),需要的朋友可以參考下2023-07-07
EditPlus運(yùn)行java時(shí)從鍵盤輸入數(shù)據(jù)的操作方法
這篇文章主要介紹了EditPlus運(yùn)行java時(shí)從鍵盤輸入數(shù)據(jù)的操作方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
Java語言實(shí)現(xiàn)簡單FTP軟件 FTP遠(yuǎn)程文件管理模塊實(shí)現(xiàn)(10)
這篇文章主要為大家詳細(xì)介紹了Java語言實(shí)現(xiàn)簡單FTP軟件,F(xiàn)TP遠(yuǎn)程文件管理模塊的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
多數(shù)據(jù)源@DS和@Transactional實(shí)戰(zhàn)
這篇文章主要介紹了多數(shù)據(jù)源@DS和@Transactional實(shí)戰(zhàn),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
SpringBoot Maven打包插件spring-boot-maven-plugin無法解析原因
spring-boot-maven-plugin是spring boot提供的maven打包插件,本文主要介紹了SpringBoot Maven打包插件spring-boot-maven-plugin無法解析原因,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03
java.io.File的renameTo方法移動文件失敗的解決方案
這篇文章主要介紹了java.io.File的renameTo方法移動文件失敗的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07

