Spring注入方式與原理分析
前言
Spring 是 Java 后端程序員必須掌握得一門(mén)框架技術(shù),Spring 的橫空出世,大大簡(jiǎn)化了企業(yè)級(jí)應(yīng)用開(kāi)發(fā)的復(fù)雜性。
Spring 框架中最核心的技術(shù)就是:
- IOC (控制反轉(zhuǎn)):是面向?qū)ο缶幊讨械囊环N設(shè)計(jì)原則,可以用來(lái)減低計(jì)算機(jī)代碼之間的耦合度(百度百科)。通俗的說(shuō),轉(zhuǎn)移對(duì)象創(chuàng)建的控制權(quán),原本對(duì)象創(chuàng)建的控制權(quán)在開(kāi)發(fā)者,現(xiàn)在通過(guò) IOC 將控制權(quán)交給 Spring ,由 Spring 統(tǒng)一管理對(duì)象的創(chuàng)建、銷(xiāo)毀等。
- AOP (切面編程):通過(guò)預(yù)編譯方式和運(yùn)行期間動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)(百度百科)。通俗的說(shuō),將一些具有公共行為(如權(quán)限校驗(yàn)、日志記錄等)封裝到可重用的公共模塊中,從何降低耦合度,提高程序的可重用性。
下面將主要介紹 Spring 中的 IOC 的依賴(lài)注入。
控制反轉(zhuǎn)IOC
IOC 主要由兩種實(shí)現(xiàn)方式:
1、依賴(lài)查找(Dependency Lookup)
容器中的受控對(duì)象通過(guò)容器的API來(lái)查找自己所依賴(lài)的資源和協(xié)作對(duì)象。通俗的說(shuō),容器幫我們創(chuàng)建好了對(duì)象,開(kāi)發(fā)者需要什么就去容器中取。
2、依賴(lài)注入(Dependency Injection,簡(jiǎn)稱(chēng) DI ,IOC 最常見(jiàn)的方式)
是對(duì)依賴(lài)查找的優(yōu)化,即無(wú)需開(kāi)發(fā)者手動(dòng)去容器中查找對(duì)象,只要告訴容器需要什么對(duì)象,容器就會(huì)將創(chuàng)建好的對(duì)象進(jìn)行注入。
依賴(lài)注入DI
在 Spring 中依賴(lài)注入的形式主要有兩種形式:
- 基于 xml 的形式
- 基于注解的形式
基于注解 DI 有三種表現(xiàn)形式:
- 基于屬性注入
- 基于屬性 setter 注入
- 基于構(gòu)造器注入
三種常規(guī)注入方式
1、 構(gòu)造器注入(Constructor Injection)
通過(guò)構(gòu)造函數(shù)來(lái)完成依賴(lài)注入,容器會(huì)在創(chuàng)建 Bean 時(shí)調(diào)用帶有參數(shù)的構(gòu)造函數(shù),并傳入所需的依賴(lài)對(duì)象。這種注入方式的優(yōu)點(diǎn)是能保證依賴(lài)在對(duì)象創(chuàng)建時(shí)就被初始化,避免了 NPE(空指針異常)的出現(xiàn)。
優(yōu)點(diǎn):
- 強(qiáng)制依賴(lài):確保 Bean 初始化時(shí)所有必需依賴(lài)都已提供。
- 不可變性:依賴(lài)對(duì)象可以聲明為
final,保證線程安全。 - 避免循環(huán)依賴(lài):Spring 在創(chuàng)建 Bean 時(shí)會(huì)檢測(cè)并拋出循環(huán)依賴(lài)異常。
示例:
public class UserService {
private final UserRepository userRepository;
// 構(gòu)造器注入(推薦使用Java 8+的構(gòu)造器自動(dòng)注入)
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
構(gòu)造器注入原理
構(gòu)造器注入是通過(guò) Bean 的構(gòu)造函數(shù)完成依賴(lài)注入的方式。
Spring 在創(chuàng)建 Bean 實(shí)例時(shí),會(huì)分析構(gòu)造函數(shù)的參數(shù),并從容器中查找匹配的依賴(lài)對(duì)象。
核心步驟
實(shí)例化前的依賴(lài)解析
當(dāng) Spring 容器啟動(dòng)并掃描到需要?jiǎng)?chuàng)建的 Bean 時(shí),首先會(huì)通過(guò)反射獲取該 Bean 的構(gòu)造函數(shù)信息(包括參數(shù)類(lèi)型和數(shù)量)。
依賴(lài)匹配
Spring 會(huì)根據(jù)構(gòu)造函數(shù)參數(shù)的類(lèi)型和名稱(chēng),從容器中查找匹配的 Bean。如果存在多個(gè)候選 Bean,可能需要通過(guò)@Qualifier或@Primary注解來(lái)明確指定。
遞歸創(chuàng)建依賴(lài)對(duì)象
如果依賴(lài)的 Bean 尚未創(chuàng)建,Spring 會(huì)先創(chuàng)建這些依賴(lài) Bean(可能同樣需要解析它們的依賴(lài)),形成遞歸過(guò)程。
調(diào)用構(gòu)造函數(shù)
當(dāng)所有依賴(lài)都準(zhǔn)備好后,Spring 使用反射調(diào)用目標(biāo) Bean 的構(gòu)造函數(shù),傳入依賴(lài)對(duì)象,完成實(shí)例化。
Spring 處理流程
- 掃描到
UserService需要?jiǎng)?chuàng)建。 - 發(fā)現(xiàn)帶參數(shù)的構(gòu)造函數(shù)
UserService(UserRepository)。 - 從容器中查找類(lèi)型為
UserRepository的 Bean。 - 如果找到,將其作為參數(shù)傳入構(gòu)造函數(shù)創(chuàng)建
UserService實(shí)例。
2、Setter 方法注入(Setter Injection)
通過(guò) setter 方法來(lái)完成依賴(lài)注入,容器會(huì)在創(chuàng)建 Bean 后底層會(huì)通過(guò)反射機(jī)制調(diào)用屬性對(duì)應(yīng)的set方法,然后給屬性賦值或者設(shè)置依賴(lài)對(duì)象。該方式的優(yōu)勢(shì)在于允許依賴(lài)對(duì)象在后期進(jìn)行修改。這種方式要求屬性必須對(duì)外提供set方法。
優(yōu)點(diǎn):
- 可選依賴(lài):允許依賴(lài)對(duì)象在 Bean 創(chuàng)建后動(dòng)態(tài)更新。
- 默認(rèn)值支持:可以在 setter 中設(shè)置默認(rèn)值。
示例:
public class UserService {
private UserRepository userRepository;
// Setter方法注入
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
Setter 注入原理
Setter 注入是通過(guò) Bean 的 setter 方法完成依賴(lài)注入的方式。Spring 先創(chuàng)建 Bean 實(shí)例,再通過(guò)反射調(diào)用 setter 方法設(shè)置依賴(lài),
Setter 注入在 Bean 實(shí)例化后執(zhí)行,依賴(lài)對(duì)象通過(guò)公開(kāi)的 setter 方法動(dòng)態(tài)設(shè)置。
核心步驟
實(shí)例化 Bean
Spring 使用無(wú)參構(gòu)造函數(shù)(或默認(rèn)構(gòu)造函數(shù))創(chuàng)建 Bean 實(shí)例。此時(shí),Bean 對(duì)象已存在,但依賴(lài)字段尚未初始化(值為null)。
依賴(lài)解析
與構(gòu)造器注入類(lèi)似,Spring 會(huì)通過(guò)反射獲取 Bean 的方法信息,識(shí)別所有 setter 方法,再根據(jù) setter 方法的參數(shù)類(lèi)型和名稱(chēng),從容器中查找匹配的依賴(lài)對(duì)象。
調(diào)用 setter 方法
通過(guò)反射調(diào)用對(duì)應(yīng)的 setter 方法,將依賴(lài)對(duì)象注入到 Bean 中。
- 若類(lèi)中沒(méi)有無(wú)參構(gòu)造函數(shù),Spring 會(huì)嘗試使用帶參數(shù)的構(gòu)造函數(shù)(需顯式配置),但這種情況更接近構(gòu)造器注入。
- Spring 掃描 Bean 的 setter 方法(符合 JavaBeans 規(guī)范的
setXxx()方法),并根據(jù)方法參數(shù)類(lèi)型從容器中查找匹配的依賴(lài)對(duì)象。
Spring 處理流程
- 使用無(wú)參構(gòu)造函數(shù)創(chuàng)建
UserService實(shí)例。 - 掃描到
setUserRepository方法。 - 從容器中查找類(lèi)型為
UserRepository的 Bean。 - 通過(guò)反射調(diào)用
setUserRepository方法,傳入依賴(lài)對(duì)象。
3、基于屬性(字段)注入(Field Injection)
日常開(kāi)發(fā)中最常使用的方式,通過(guò)反射直接注入字段,使用@Autowired、@Resource或@Inject注解標(biāo)記字段。
字段注入是利用反射機(jī)制直接對(duì)類(lèi)的字段進(jìn)行注入,無(wú)需借助構(gòu)造方法或 setter 方法。雖然這種方式代碼更為簡(jiǎn)潔,但它也存在一些缺點(diǎn),比如無(wú)法應(yīng)用于不可變對(duì)象,并且在進(jìn)行單元測(cè)試時(shí)需要使用反射來(lái)設(shè)置依賴(lài)。
@Service
public class UserServiceImpl implements UserService {
@Autowired
private PhoneService phoneService;
}
@Service
public class UserServiceImpl implements UserService {
@Resource
private PhoneService phoneService;
}
@Autowired 注解和 @Resource 注解的區(qū)別:
| @Autowired | @Resource |
|---|---|
| Spring 的注解,它的包是 org.springframework.beans.factory.annotation.Autowired | 不是Spring的注解,它的包是 javax.annotation.Resource |
| 只按照 byType 注入 | 默認(rèn)按照 byName 自動(dòng)注入 |
| 無(wú)屬性 | 有兩個(gè)重要的屬性:name 和 type |
@Resource的裝配順序
- 如果同時(shí)指定了 name 和 type ,則從 Spring 上下文中找到唯一匹配的 bean 進(jìn)行裝配,找不到則拋出異常。
- 如果指定了 name ,則從上下文中查找名稱(chēng)匹配的 bean 進(jìn)行裝配,找不到則拋出異常。
- 如果指定了 type ,則從上下文中**查找類(lèi)型匹配的唯一 **bean 進(jìn)行裝配,找不到或是找到多個(gè),都會(huì)拋出異常。
- 如果既沒(méi)有指定 name ,又沒(méi)有指定 type ,則自動(dòng)按照 byName 方式進(jìn)行裝配;如果沒(méi)有匹配,則回退為一個(gè)原始類(lèi)型進(jìn)行匹配,如果匹配則自動(dòng)裝配。
為何 Spring 官方推薦使用構(gòu)造器注入呢?
- 保證依賴(lài)不可變(final關(guān)鍵字)
- 保證依賴(lài)不為空(省去了程序啟動(dòng)因注入對(duì)象為空而報(bào)異常)
- 避免循環(huán)依賴(lài)
- 提升了代碼的可復(fù)用性(非 IOC 環(huán)境下,可使用 new 實(shí)例化該類(lèi)的對(duì)象)
接口多實(shí)現(xiàn)類(lèi)注入
當(dāng)我們用上面三種方式注入接口時(shí),接口有多個(gè)實(shí)現(xiàn)類(lèi)時(shí),程序啟動(dòng)就會(huì)報(bào)錯(cuò),因?yàn)?Spring 不知道要注入哪個(gè)實(shí)現(xiàn)類(lèi)。
public interface PaymentProcessor {
void processPayment(double amount);
}
public class AlipayProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount) {
System.out.println("Processing payment via Alipay: " + amount);
}
}
public class WechatProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount) {
System.out.println("Processing payment via WeChat: " + amount);
}
}
@Component
public class OrderService {
private final PaymentProcessor processor; // 報(bào)錯(cuò):存在多個(gè)候選Bean
public OrderService(PaymentProcessor processor) {
this.processor = processor;
}
}

那么要如何解決接口多實(shí)現(xiàn)類(lèi)的注入問(wèn)題呢?
- 1、定義的時(shí)候指定Bean名稱(chēng)
public interface PaymentProcessor {
void processPayment(double amount);
}
@Component("alipayProcessor")
public class AlipayProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount) {
System.out.println("Processing payment via Alipay: " + amount);
}
}
@Component("wechatProcessor")
public class WechatProcessor implements PaymentProcessor {
@Override
public void processPayment(double amount) {
System.out.println("Processing payment via WeChat: " + amount);
}
}
@Component
public class OrderService {
private final PaymentProcessor processor; // 報(bào)錯(cuò):存在多個(gè)候選Bean
public OrderService(PaymentProcessor processor) {
this.processor = processor;
}
}
通過(guò) @Autowired 注解結(jié)合 @Qualifier 注解
@Service
public class UserServiceImpl implements UserService {
@Autowired
@Qualifier("alipayProcessor")
private PaymentProcessor processor;
}
@Qualifier("applePhoneServieImpl") 指定要引入的具體實(shí)現(xiàn)類(lèi)。
通過(guò) @Resource 注解動(dòng)態(tài)獲取
@Service
public class UserServiceImpl implements UserService {
@Resource(name = "alipayProcessor")
private PhoneService phoneService;
}
通過(guò) @Primary 注解優(yōu)先注入
@Component
@Primary // 優(yōu)先注入此類(lèi)
public class AlipayProcessor implements PaymentProcessor { ... }
@Primary 注解表示當(dāng)有多個(gè) bean 滿足注入條件時(shí),會(huì)優(yōu)先注入該注解修飾的 bean 。
通過(guò) @ConditionalOnProperty 注解結(jié)合配置文件注入
@Configuration
public class PaymentConfig {
@Bean
@ConditionalOnProperty(prefix = "payment", name = "processor", havingValue = "alipay")
public PaymentProcessor alipayProcessor() {
return new AlipayProcessor();
}
@Bean
@ConditionalOnProperty(prefix = "payment", name = "processor", havingValue = "wechat")
public PaymentProcessor wechatProcessor() {
return new WechatProcessor();
}
}
配置文件application.properties:
payment.processor=alipay
@ConditionalOnProperty(prefix = "payment", name = "processor", havingValue = "alipay") 意指當(dāng)配置文件中 payment.processor=alipay 時(shí),AlipayProcessor 才會(huì)注入到容器中。
通過(guò)其他 @Conditional條件注解
@ConditionalOnBean:當(dāng)存在某一個(gè) Bean 時(shí),初始化此類(lèi)到容器。@ConditionalOnClass:當(dāng)存在某一個(gè)類(lèi)時(shí),初始化此類(lèi)的容器。@ConditionalOnMissingBean:當(dāng)不存在某一個(gè) Bean 時(shí),初始化此類(lèi)到容器。@ConditionalOnMissingClass:當(dāng)不存在某一個(gè)類(lèi)時(shí),初始化此類(lèi)到容器。- …
@Component
@ConditionalOnClass(name = "com.alipay.sdk.app.PayTask") // 存在Alipay SDK時(shí)生效
public class AlipayProcessor implements PaymentProcessor { ... }
@Component
@ConditionalOnMissingBean(PaymentProcessor.class) // 沒(méi)有其他實(shí)現(xiàn)時(shí)生效
public class DefaultPaymentProcessor implements PaymentProcessor { ... }
- 通過(guò)集合注入
@Service
public class UserServiceImpl implements UserService {
@Autowired
private List<PaymentProcessor> processors;
@Autowired
private Map<String, PaymentProcessor> aymentProcessorMap;
}
關(guān)鍵區(qū)別與適用場(chǎng)景
| 方案 | 核心機(jī)制 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|---|
| @Qualifier | 顯式指定 Bean 名稱(chēng) | 明確、直觀 | 需要硬編碼 Bean 名稱(chēng) |
| @Resource | JSR-250 標(biāo)準(zhǔn),按名稱(chēng)注入 | 兼容性好 | 依賴(lài)命名約定 |
| @Primary | 標(biāo)記首選實(shí)現(xiàn) | 減少配置 | 只能有一個(gè)主實(shí)現(xiàn) |
| @Conditional | 根據(jù)條件動(dòng)態(tài)選擇 | 靈活、可配置 | 條件邏輯可能復(fù)雜 |
| 集合注入 | 注入所有實(shí)現(xiàn)到列表 / Map | 運(yùn)行時(shí)動(dòng)態(tài)選擇 | 需要額外代碼處理集合 |
獲取Bean的方式
在 Spring 項(xiàng)目中,有時(shí)候需要手動(dòng)去獲取 bean,手動(dòng)獲取的方式需要通過(guò) applicationContext ,有以下幾種形式:
通過(guò) applicationContext 獲取
@Service
public class UserServiceImpl implements UserService {
@Autowired
private ApplicationContext applicationContext;
public Object getBean() {
return applicationContext.getBean("appleService");
}
}
實(shí)現(xiàn) ApplicationContextAware 接口
@Component
public class SpringContextUtil implements ApplicationContextAware {
public static ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextUtil.applicationContext = applicationContext;
}
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
public static <T> T getBean(String name, Class<T> clazz) {
return applicationContext.getBean(name, clazz);
}
public static Boolean containsBean(String name) {
return applicationContext.containsBean(name);
}
public static Boolean isSingleton(String name) {
return applicationContext.isSingleton(name);
}
public static Class<? extends Object> getType(String name) {
return applicationContext.getType(name);
}
}
PhoneService phoneService = SpringContextUtil.getBean(PhoneService.class);
繼承自抽象類(lèi) ApplicationObjectSupport
@Component
public class SpringContextHelper extends ApplicationObjectSupport {
public Object getBean(String name) {
return getApplicationContext().getBean(name);
}
public <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
public <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
public Boolean containsBean(String name) {
return getApplicationContext().containsBean(name);
}
public Boolean isSingleton(String name) {
return getApplicationContext().isSingleton(name);
}
public Class<? extends Object> getType(String name) {
return getApplicationContext().getType(name);
}
}
繼承自抽象類(lèi) WebApplicationObjectSupport
WebApplicationObjectSupport 繼承了 ApplicationObjectSupport ,所以使用方法和上面一樣。
總結(jié)
本文主要記錄了 Spring 中基于注解的 DI 幾種方式,接口多個(gè)實(shí)現(xiàn)的注入方式,如何通過(guò) applicationContext 手動(dòng)獲取 bean 。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java調(diào)用opencv實(shí)現(xiàn)圖片矯正功能
這篇文章主要為大家詳細(xì)介紹了Java如何調(diào)用opencv實(shí)現(xiàn)圖片矯正功能,文中的示例代碼簡(jiǎn)潔易懂,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-09-09
SpringBoot項(xiàng)目調(diào)優(yōu)及垃圾回收器的比較詳解
這篇文章主要介紹了SpringBoot項(xiàng)目調(diào)優(yōu)及垃圾回收器的比較詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
Springboot 自定義線程池的參數(shù)配置最優(yōu)小結(jié)
在SpringBoot中配置自定義線程池時(shí),參數(shù)的設(shè)置需要根據(jù)具體的應(yīng)用場(chǎng)景、系統(tǒng)資源和業(yè)務(wù)需求來(lái)調(diào)整,本文就來(lái)詳細(xì)的介紹一下,感興趣的可以了解一下2025-10-10
SpringBoot實(shí)現(xiàn)多數(shù)據(jù)源配置的示例詳解
這篇文章主要為大家詳細(xì)介紹了SpringBoot實(shí)現(xiàn)多數(shù)據(jù)源配置的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-12-12
Java程序設(shè)計(jì)之12個(gè)經(jīng)典樣例
這篇文章主要給大家分享關(guān)于Java程序設(shè)計(jì)11個(gè)經(jīng)典樣例,主要以舉例的形式詳細(xì)的講解了Java程序設(shè)計(jì)的各種方法,需要的朋友可以參考一下文章具體的內(nèi)容2021-10-10
java中MultipartFile類(lèi)型轉(zhuǎn)為File類(lèi)型的4種方法
Spring提供了一個(gè)MultipartFile接口來(lái)處理文件上傳,但有時(shí)候我們需要將MultipartFile轉(zhuǎn)換為File來(lái)進(jìn)行一些特定的操作,比如保存文件到本地或者進(jìn)行文件的處理等,這篇文章主要給大家介紹了關(guān)于java中MultipartFile類(lèi)型轉(zhuǎn)為File類(lèi)型的4種方法,需要的朋友可以參考下2024-09-09
[Spring MVC]-詳解SpringMVC的各種參數(shù)綁定方式
本篇文章主要介紹了SpringMVC的各種參數(shù)綁定方式 ,具有一定的參考價(jià)值,有需要的可以了解一下。2016-12-12

