Spring核心思想之淺談IoC容器與依賴倒置(DI)
在日常開發(fā)中,我們總會(huì)面臨一個(gè)問(wèn)題:如何優(yōu)雅地管理對(duì)象的創(chuàng)建和依賴? 你可能會(huì)寫一堆代碼來(lái)手動(dòng)構(gòu)造對(duì)象,但這種方式繁瑣且難以維護(hù)。而當(dāng)項(xiàng)目變得復(fù)雜,依賴鏈拉長(zhǎng),手動(dòng)管理對(duì)象的方式很快就會(huì)捉襟見肘。
這時(shí),Spring 的 IoC 和 DI 機(jī)制便是解放雙手的利器,它讓開發(fā)者專注于業(yè)務(wù)邏輯,容器則負(fù)責(zé)對(duì)象的創(chuàng)建與依賴管理。與此同時(shí),MyBatis 的動(dòng)態(tài)代理更是省去了為每個(gè)接口手動(dòng)實(shí)現(xiàn)類的麻煩,極大地提高了效率。
但你有沒有想過(guò),Spring 是如何找到你的類并自動(dòng)注入依賴的?MyBatis 又是如何在沒有實(shí)現(xiàn)類的情況下完成數(shù)據(jù)庫(kù)操作的?如果你也有這些疑問(wèn),那恭喜你,今天的內(nèi)容正是為你準(zhǔn)備的!
一、控制反轉(zhuǎn) IoC
控制反轉(zhuǎn)(Inversion of Control, IoC) 是一種設(shè)計(jì)思想。它強(qiáng)調(diào)將控制權(quán)從對(duì)象本身轉(zhuǎn)移到容器中。服務(wù)中心(容器)負(fù)責(zé)管理各種資源(對(duì)象和依賴)。用戶(對(duì)象)需要資源時(shí),服務(wù)中心將資源提供給它。容器控制對(duì)象的創(chuàng)建、依賴注入和生命周期管理。
在傳統(tǒng)編程中,對(duì)象需要自己控制依賴的創(chuàng)建和管理;在 IoC 中,這些任務(wù)由容器負(fù)責(zé)。
容器根據(jù) Bean 的依賴關(guān)系,通過(guò) DI 注入所需的依賴。
二、依賴倒置 DI
1. 詳細(xì)概念
依賴注入(Dependency Injection, DI) 是 IoC 的具體實(shí)現(xiàn)方式之一。
它通過(guò)注入的方式,將對(duì)象需要的依賴提供給它,而不是由對(duì)象自己去創(chuàng)建,Spring會(huì)按需創(chuàng)建相應(yīng)的對(duì)象,通過(guò)構(gòu)造器、Setter 方法或字段注入,把依賴傳遞給對(duì)象。
2. Spring 中 DI 的實(shí)現(xiàn)原理
- 聲明依賴:使用注解(
@Component
、@Service
、@Repository
)將類標(biāo)記為 Spring 容器管理的 Bean。 - 注入依賴:Spring 在啟動(dòng)時(shí)掃描類路徑,自動(dòng)檢測(cè)依賴,并通過(guò)
@Autowired
注解注入相應(yīng)的Bean。 - 容器提供依賴:Spring 容器會(huì)根據(jù)配置文件或注解,實(shí)例化對(duì)象并注入到需要的地方。
三、注冊(cè)Bean過(guò)程:以 Spring+Mybatis 為例
1. Spring 是如何通過(guò)注解注冊(cè) Bean 的
Spring 通過(guò) 組件掃描(Component Scanning) 和 注解識(shí)別 將類注冊(cè)為 Bean。
- 注解識(shí)別:包括
@Component
、@Service
、@Repository
、@Controller
等。 - 特定集成注解:如 MyBatis 的
@Mapper
,它告訴 Spring 將標(biāo)注的接口注冊(cè)為 Bean,并交由 MyBatis 動(dòng)態(tài)代理生成實(shí)現(xiàn)類。
注冊(cè)過(guò)程:
- Spring 啟動(dòng)時(shí)會(huì)掃描指定的包路徑。
- 找到標(biāo)注了這些注解的類或接口,并注冊(cè)到 IoC 容器中,形成 Bean 定義。
2. MyBatis是如何動(dòng)態(tài)生成 UserMapper 的實(shí)現(xiàn)類的
UserMapper 是接口,沒有具體實(shí)現(xiàn)類。MyBatis 會(huì)利用 @Mapper
注解,結(jié)合 Mapper 配置文件或注解中的 SQL 語(yǔ)句,動(dòng)態(tài)生成代理實(shí)現(xiàn)類。
代理類生成過(guò)程:
動(dòng)態(tài)代理機(jī)制:MyBatis 使用 JDK 動(dòng)態(tài)代理,為每個(gè) Mapper 接口生成一個(gè)代理類。
InvocationHandler:代理類攔截所有對(duì)接口方法的調(diào)用,將它們轉(zhuǎn)發(fā)到 MyBatis 的核心組件(如 SqlSession
)執(zhí)行 SQL。
- 執(zhí)行 SQL:
- 根據(jù)方法名或注解,定位 SQL 配置。
- 使用 MyBatis 的
Executor
執(zhí)行 SQL 并返回結(jié)果。
3. @Autowired 注入過(guò)程
- 掃描 Bean:Spring 啟動(dòng)時(shí),掃描
UserServiceImpl
和UserMapper
,分別標(biāo)注了@Service
和@Mapper
,將它們注冊(cè)為 Bean。 - 識(shí)別依賴:Spring 在注冊(cè)
UserServiceImpl
Bean 時(shí),檢測(cè)到其字段userMapper
被標(biāo)注了@Autowired
,即是否依賴于其他 Bean。
【注入邏輯】
找到目標(biāo) Bean:
- 在 IoC 容器中,根據(jù)類型
UserMapper
查找對(duì)應(yīng)的 Bean。 - 如果找到多個(gè)匹配 Bean,Spring 會(huì)結(jié)合 Bean 名稱或
@Qualifier
注解解決沖突。
依賴注入:
- Spring 使用 Java 反射機(jī)制為
userMapper
字段賦值。 - 具體實(shí)現(xiàn)偽代碼如下:
// 獲取字段 Field field = UserServiceImpl.class.getDeclaredField("userMapper"); // 使私有字段可訪問(wèn) field.setAccessible(true); // 將找到的 UserMapper Bean 注入到 userServiceImpl 實(shí)例 field.set(userServiceImplInstance, userMapperBean);
4. 總結(jié):Spring 與 MyBatis 的結(jié)合
Spring:
- 提供 IoC 容器,掃描 Bean,處理依賴注入。
- 通過(guò)反射將
UserMapper
動(dòng)態(tài)代理對(duì)象注入到UserServiceImpl
。
MyBatis:
- 動(dòng)態(tài)生成
UserMapper
的代理實(shí)現(xiàn)類,負(fù)責(zé)將方法調(diào)用轉(zhuǎn)化為 SQL 查詢。 - 代理類中通過(guò)
InvocationHandler
將方法調(diào)用委托給 MyBatis 的 SQL 執(zhí)行器。
附加:代理類與 UserMapper 實(shí)現(xiàn)類的差異
代理類:
- 動(dòng)態(tài)生成,沒有手寫實(shí)現(xiàn)代碼。
- 通過(guò)攔截接口方法,轉(zhuǎn)發(fā)到 MyBatis 核心組件處理。
普通實(shí)現(xiàn)類:
- 靜態(tài)定義,需手動(dòng)實(shí)現(xiàn)每個(gè)方法的邏輯。
示例對(duì)比:
// 動(dòng)態(tài)代理生成的代理類示例 public class UserMapperProxy implements UserMapper { private final SqlSession sqlSession; ? public UserMapperProxy(SqlSession sqlSession) { this.sqlSession = sqlSession; } ? @Override public User findById(int id) { // 將方法調(diào)用轉(zhuǎn)化為 MyBatis 的 SQL 執(zhí)行 return sqlSession.selectOne("namespace.findById", id); } } ? // 普通實(shí)現(xiàn)類(手動(dòng)實(shí)現(xiàn)) public class UserMapperImpl implements UserMapper { @Override public User findById(int id) { // 自己寫邏輯,連接數(shù)據(jù)庫(kù),執(zhí)行 SQL return executeSQL("SELECT * FROM user WHERE id = ?", id); } }
動(dòng)態(tài)代理的優(yōu)勢(shì)在于:
- 代碼復(fù)用性高:只需定義接口和 SQL,無(wú)需重復(fù)寫實(shí)現(xiàn)類。
- 與 SQL 配置無(wú)縫對(duì)接:方便維護(hù)和管理 SQL 語(yǔ)句。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java實(shí)現(xiàn)基于SGIP協(xié)議開發(fā)聯(lián)通短信的方法
這篇文章主要介紹了java實(shí)現(xiàn)基于SGIP協(xié)議開發(fā)聯(lián)通短信的方法,涉及java短信發(fā)送的相關(guān)實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07Spring-AOP自動(dòng)創(chuàng)建代理之BeanNameAutoProxyCreator實(shí)例
這篇文章主要介紹了Spring-AOP自動(dòng)創(chuàng)建代理之BeanNameAutoProxyCreator實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07java.lang.Void 與 void的比較及使用方法介紹
這篇文章主要介紹了java.lang.Void 與 void的比較及使用方法介紹,小編覺得挺不錯(cuò)的,這里給大家分享一下,需要的朋友可以參考。2017-10-10使用Java8?Stream流的skip?+?limit實(shí)現(xiàn)批處理的方法
Stream 作為 Java 8 的一大亮點(diǎn),它與 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念這篇文章主要介紹了使用Java8?Stream流的skip?+?limit實(shí)現(xiàn)批處理,需要的朋友可以參考下2022-07-07淺談Spring裝配Bean之組件掃描和自動(dòng)裝配
本篇文章主要介紹了淺談Spring裝配Bean之組件掃描和自動(dòng)裝配,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10java 反射getClass .class 的使用方法示例
這篇文章主要介紹了java 反射getClass .class 的使用方法,結(jié)合實(shí)例形式分析了java類反射機(jī)制的相關(guān)操作技巧,需要的朋友可以參考下2019-11-11Mybatis order by 動(dòng)態(tài)傳參出現(xiàn)的問(wèn)題及解決方法
今天,我正在愉快地CRUD,突然發(fā)現(xiàn)出現(xiàn)一個(gè)Bug,我們來(lái)看看是怎么回事吧!接下來(lái)通過(guò)本文給大家介紹Mybatis order by 動(dòng)態(tài)傳參出現(xiàn)的一個(gè)小bug,需要的朋友可以參考下2021-07-07