Spring?AOP底層原理及代理模式
Spring AOP底層原理代理模式
一、什么是 AOP
AOP 就是面向切面編程,是 OOP(面向?qū)ο缶幊?的延續(xù)。
利用 AOP 可以對業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序可用性,同時(shí)也提高了開發(fā)效率。
通俗一點(diǎn)說,不用修改原代碼,可以給原代碼增加新的功能。
二、AOP 底層原理
AOP 底層原理是使用動(dòng)態(tài)代理。
那代理是什么?有動(dòng)態(tài)代理,那是不是還有靜態(tài)代理?
1. 什么是代理?
就是為一個(gè)目標(biāo)對象提供一個(gè)代理對象,并由代理對象控制對目標(biāo)對象的引用。使用代理對象,是為了在不修改目標(biāo)對象的基礎(chǔ)上,增強(qiáng)目標(biāo)對象的業(yè)務(wù)邏輯。
比如目標(biāo)對象 A,代理對象是 B。
- 那么現(xiàn)在 B 對 A 進(jìn)行引用,可以實(shí)現(xiàn) A 有的功能。
- 另外,B 還可以在自身進(jìn)行一些新功能,最終不需要修改目標(biāo)對象 A 。
而代理分為靜態(tài)代理和動(dòng)態(tài)代理,區(qū)別是:
靜態(tài)代理有真實(shí)的代理類存在,就是我們會代碼中創(chuàng)建一個(gè)代理類,并在代理類的方法中調(diào)用目標(biāo)對象的方法,以此來完成代理的工作。動(dòng)態(tài)代理的代理類沒有在代碼中創(chuàng)建一個(gè)代理類,而是在運(yùn)行時(shí)在JVM里面創(chuàng)建代理對象。
2. 什么是靜態(tài)代理
靜態(tài)代理是有實(shí)實(shí)在在的代理類存在,并且和目標(biāo)類實(shí)現(xiàn)相同的接口。
比如,有一個(gè)轉(zhuǎn)賬業(yè)務(wù),現(xiàn)在希望給它增加功能,使在轉(zhuǎn)賬之前確認(rèn)轉(zhuǎn)賬人身份,以及轉(zhuǎn)賬之后通知收款人。
(1) 接口 AccountServiceDao :
package com.pingguo.spring5.dao; public interface AccountServiceDao { // 主業(yè)務(wù)邏輯,轉(zhuǎn)賬 void transfer(); }
(2) 接口 AccountServiceDao 的實(shí)現(xiàn)類:
package com.pingguo.spring5.dao; public class AccountServiceImpl implements AccountServiceDao { @Override public void transfer() { System.out.println("調(diào)用dao層,完成轉(zhuǎn)賬主業(yè)務(wù)."); } }
(3) 代理類 AccountProxy :
package com.pingguo.spring5.proxy; import com.pingguo.spring5.dao.AccountServiceDao; public class AccountProxy implements AccountServiceDao { // 目標(biāo)對象 private AccountServiceDao target; public AccountProxy(AccountServiceDao target) { this.target = target; } /** * 代理方法,實(shí)現(xiàn)對目標(biāo)方法的增強(qiáng) */ @Override public void transfer() { before(); target.transfer(); after(); } /** * 增強(qiáng)的功能,轉(zhuǎn)賬之前使用 */ private void before() { System.out.println("對轉(zhuǎn)賬人身份進(jìn)行驗(yàn)證."); } /** * 增強(qiáng)的功能,轉(zhuǎn)賬之后使用 */ private void after() { System.out.println("轉(zhuǎn)賬完成,已通知收款人."); } }
在代理類中:
- 添加了添加了目標(biāo)對象,并且有參構(gòu)造方法里需要傳入目標(biāo)對象。
- 代理方法里,調(diào)用了目標(biāo)對象里的轉(zhuǎn)賬方法 target.transfer()。
- before() 和 after() 則是 2個(gè)增強(qiáng)的方法,分別作用于 target.transfer() 的前面和后面。
(4) 運(yùn)行測試新建一個(gè)測試方法,運(yùn)行看下結(jié)果:
@Test public void testProxy() { // 創(chuàng)建目標(biāo)對象 AccountServiceDao target = new AccountServiceImpl(); // 創(chuàng)建代理對象 AccountProxy proxy = new AccountProxy(target); proxy.transfer(); }
- 這里先創(chuàng)建了目標(biāo)對象
- 再創(chuàng)建代理對象,并且把目標(biāo)對象傳入
- 最后調(diào)用代理對象里的,被增強(qiáng)過的方法 transfer()。
結(jié)果:
對轉(zhuǎn)賬人身份進(jìn)行驗(yàn)證. 調(diào)用dao層,完成轉(zhuǎn)賬主業(yè)務(wù). 轉(zhuǎn)賬完成,已通知收款人. Process finished with exit code 0
優(yōu)點(diǎn):
- 效率高,因?yàn)樗械念惗际且呀?jīng)編寫完成的,使用的時(shí)候只需要取得代理對象并且執(zhí)行即可。
- 同時(shí)也可以實(shí)現(xiàn)對目標(biāo)對象中指定的方法進(jìn)行增強(qiáng)。
缺點(diǎn):
- 與目標(biāo)類實(shí)現(xiàn)相同的接口代碼,冗余。
- 如果接口發(fā)生改變,代理類中的方法也要修改。
- 代理類服務(wù)于一種類型的對象,如果要服務(wù)多類型的對象,那么要為每種類型的對象都生成代理類。
3. 什么是動(dòng)態(tài)代理
與靜態(tài)代理的硬編碼方式相比,動(dòng)態(tài)代理支持運(yùn)行時(shí)動(dòng)態(tài)生成代理對象這種方式。換句話說,動(dòng)態(tài)代理并不存在代理類,代理對象直接由代理生成工具動(dòng)態(tài)生成。
優(yōu)點(diǎn):
- 用很少的代碼對一個(gè)類的所有方法實(shí)現(xiàn)一樣的增強(qiáng)效果。
- 在編碼時(shí),代理邏輯與業(yè)務(wù)邏輯互相獨(dú)立,各不影響,減少侵入,降低耦合。
缺點(diǎn):
相對于靜態(tài)代理,它不能增強(qiáng)其中的某一個(gè)方法。
對于動(dòng)態(tài)代理,針對于是否存在接口的情況下,又分為 2 種:
- 有接口的情況下,使用 JDK 動(dòng)態(tài)代理。
- 無接口的情況下,使用 CGLIB 動(dòng)態(tài)代理。
使用 JDK 動(dòng)態(tài)代理
使用 JDK 動(dòng)態(tài)代理,創(chuàng)建的是接口實(shí)現(xiàn)類的代理對象,以此來實(shí)現(xiàn)功能增強(qiáng)。
現(xiàn)在不需要上面創(chuàng)建過的實(shí)際代理類了 。
接口,為了后面的一些知識點(diǎn)的說明,里面加個(gè)參數(shù),轉(zhuǎn)賬的金額:
package com.pingguo.spring5.dao; public interface AccountServiceDao { // 主業(yè)務(wù)邏輯,轉(zhuǎn)賬 void transfer(int amount); }
實(shí)現(xiàn)類:
package com.pingguo.spring5.dao; public class AccountServiceImpl implements AccountServiceDao { @Override public void transfer(int amount) { System.out.println("調(diào)用dao層,完成轉(zhuǎn)賬主業(yè)務(wù).金額:" + amount); } }
在測試方法里,直接使用動(dòng)態(tài)代理:
@Test public void testDynamicProxy() { // 創(chuàng)建目標(biāo)對象 AccountServiceDao target = new AccountServiceImpl(); // 創(chuàng)建代理對象 AccountServiceDao proxy = (AccountServiceDao) Proxy.newProxyInstance( target.getClass().getClassLoader(), // 目標(biāo)類使用的類加載器 target.getClass().getInterfaces(), // 目標(biāo)類實(shí)現(xiàn)的接口 new InvocationHandler() { // 調(diào)用處理器 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("對轉(zhuǎn)賬人身份進(jìn)行驗(yàn)證."); Object res = method.invoke(target, args); System.out.println("轉(zhuǎn)賬完成,已通知收款人."); return res; } } ); // 讓代理工作 proxy.transfer(10000); }
運(yùn)行結(jié)果:
對轉(zhuǎn)賬人身份進(jìn)行驗(yàn)證. 調(diào)用dao層,完成轉(zhuǎn)賬主業(yè)務(wù).金額:10000 轉(zhuǎn)賬完成,已通知收款人. Process finished with exit code 0
動(dòng)態(tài)代理的過程:
- 創(chuàng)建處理器 InvocationHandler實(shí)例。
- 在調(diào)用目標(biāo)對象時(shí),會調(diào)用代理對象。
- 代理對象去請求目標(biāo)對象。invoke 方法就是調(diào)用目標(biāo)對象的方法生成代理對象的過程。
- 同時(shí),在 invoke 方法中進(jìn)行功能增強(qiáng)。
對于 invoke 中的 3 個(gè)參數(shù),分別是:
- Object proxy:代理對象,一般不會使用。
- Method method:外面的代理對象調(diào)用的方法引用,這里引用的就是 transfer()
- Object[] args:外面的代理對象調(diào)用的方法里面的參數(shù),這里就是參數(shù) amount。
使用 CGLIB 動(dòng)態(tài)代理
CGLIB動(dòng)態(tài)代理的原理是生成目標(biāo)類的子類,這個(gè)子類對象就是代理對象,代理對象是被增強(qiáng)過的。
注意,不管有沒有接口都可以使用 CGLIB 動(dòng)態(tài)代理, 而不是只有在無接口的情況下才能使用。
示例就暫時(shí)不放了,因?yàn)槲冶镜丨h(huán)境問題,有個(gè)報(bào)錯(cuò)始終未解決,后續(xù)再說,不影響繼續(xù)學(xué)習(xí) spring。
以上就是Spring AOP底層原理及代理模式的詳細(xì)內(nèi)容,更多關(guān)于Spring AOP底層原理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中線程狀態(tài)+線程安全問題+synchronized的用法詳解
這篇文章主要介紹了Java中線程狀態(tài)+線程安全問題+synchronized的用法詳解,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04springboot如何使用redis的incr創(chuàng)建分布式自增id
這篇文章主要介紹了springboot如何使用redis的incr創(chuàng)建分布式自增id,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11在Java和PostgreSQL枚舉之間轉(zhuǎn)換的通用方法
枚舉類型(enum)是一種方便的數(shù)據(jù)類型,允許我們指定一個(gè)常量列表,對象字段或數(shù)據(jù)庫列可以設(shè)置為該列表中的值,在本文中,我將回顧處理Java和PostgreSQL枚舉轉(zhuǎn)換的通用方法,需要的朋友可以參考下2023-10-10SpringBoot使用slf4j日志并輸出到文件中的操作方法
這篇文章主要介紹了SpringBoot使用slf4j日志并輸出到文件中,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08