深入淺析Spring 的aop實(shí)現(xiàn)原理
什么是AOP
AOP(Aspect-OrientedProgramming,面向方面編程),可以說是OOP(Object-Oriented Programing,面向?qū)ο缶幊蹋┑难a(bǔ)充和完善。OOP引入封裝、繼承和多態(tài)性等概念來建立一種對象層次結(jié)構(gòu),用以模擬公共行為的一個集合。當(dāng)我們需要為分散的對象引入公共行為的時候,OOP則顯得無能為力。也就是說,OOP允許你定義從上到下的關(guān)系,但并不適合定義從左到右的關(guān)系。例如日志功能。日志代碼往往水平地散布在所有對象層次中,而與它所散布到的對象的核心功能毫無關(guān)系。對于其他類型的代碼,如安全性、異常處理和透明的持續(xù)性也是如此。這種散布在各處的無關(guān)的代碼被稱為橫切(cross-cutting)代碼,在OOP設(shè)計(jì)中,它導(dǎo)致了大量代碼的重復(fù),而不利于各個模塊的重用。
簡介
前段時間寫的java設(shè)計(jì)模式--代理模式,最近在看Spring Aop的時候,覺得于代理模式應(yīng)該有密切的聯(lián)系,于是決定了解下Spring Aop的實(shí)現(xiàn)原理。
說起AOP就不得不說下OOP了,OOP中引入封裝、繼承和多態(tài)性等概念來建立一種對象層次結(jié)構(gòu),用以模擬公共行為的一個集合。但是,如果我們需要為部分對象引入公共部分的時候,OOP就會引入大量重復(fù)的代碼。例如:日志功能。
AOP技術(shù)利用一種稱為“橫切”的技術(shù),解剖封裝的對象內(nèi)部,并將那些影響了多個類的公共行為封裝到一個可重用模塊,這樣就能減少系統(tǒng)的重復(fù)代碼,降低模塊間的耦合度,并有利于未來的可操作性和可維護(hù)性。AOP把軟件系統(tǒng)分為兩個部分:核心關(guān)注點(diǎn)和橫切關(guān)注點(diǎn)。業(yè)務(wù)處理的主要流程是核心關(guān)注點(diǎn),與之關(guān)系不大的部分是橫切關(guān)注點(diǎn)。橫切關(guān)注點(diǎn)的一個特點(diǎn)是,他們經(jīng)常發(fā)生在核心關(guān)注點(diǎn)的多處,而各處都基本相似。比如權(quán)限認(rèn)證、日志、事務(wù)處理。
實(shí)現(xiàn)原理
前面在學(xué)習(xí)代理模式的時候,了解到代理模式分為動態(tài)代理和靜態(tài)代理?,F(xiàn)在我們就以代理模式為基礎(chǔ)先實(shí)現(xiàn)我們自己的AOP框架,再來研究Spring的AOP的實(shí)現(xiàn)原理。
先以靜態(tài)代理實(shí)現(xiàn),靜態(tài)代理關(guān)鍵是在代理對象和目標(biāo)對象實(shí)現(xiàn)共同的接口,并且代理對象持有目標(biāo)對象的引用。
公共接口代碼:
public interface IHello { /** * 業(yè)務(wù)方法 * @param str */ void sayHello(String str); } 目標(biāo)類代碼: public class Hello implements IHello{ @Override public void sayHello(String str) { System.out.println("hello "+str); } }
代理類代碼,我們給它添加日志記錄功能,在方法開始前后執(zhí)行特定的方法,是不是和AOP特別像呢?
public class ProxyHello implements IHello{ private IHello hello; public ProxyHello(IHello hello) { super(); this.hello = hello; } @Override public void sayHello(String str) { Logger.start();//添加特定的方法 hello.sayHello(str); Logger.end(); } }
日志類代碼:
public class Logger { public static void start(){ System.out.println(new Date()+ " say hello start..."); } public static void end(){ System.out.println(new Date()+ " say hello end"); } }
測試代碼:
public class Test { public static void main(String[] args) { IHello hello = new ProxyHello(new Hello());//如果我們需要日志功能,則使用代理類 //IHello hello = new Hello();//如果我們不需要日志功能則使用目標(biāo)類 hello.sayHello("明天"); } }
這樣我們就實(shí)現(xiàn)了一個最簡單的AOP,但是這樣會存在一個問題:如果我們像Hello這樣的類很多,那么,我們是不是要去寫很多個HelloProxy這樣的類呢。其實(shí)也是一種很麻煩的事。在jdk1.3以后,jdk跟我們提供了一個API java.lang.reflect.InvocationHandler的類, 這個類可以讓我們在JVM調(diào)用某個類的方法時動態(tài)的為些方法做些什么事。下面我們就來實(shí)現(xiàn)動態(tài)代理的實(shí)現(xiàn)。
動態(tài)代理實(shí)現(xiàn)主要是實(shí)現(xiàn)InvocationHandler,并且將目標(biāo)對象注入到代理對象中,利用反射機(jī)制來執(zhí)行目標(biāo)對象的方法。
接口實(shí)現(xiàn)與靜態(tài)代理相同,代理類代碼:
public class DynaProxyHello implements InvocationHandler{ private Object target;//目標(biāo)對象 /** * 通過反射來實(shí)例化目標(biāo)對象 * @param object * @return */ public Object bind(Object object){ this.target = object; return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; Logger.start();//添加額外的方法 //通過反射機(jī)制來運(yùn)行目標(biāo)對象的方法 result = method.invoke(this.target, args); Logger.end(); return result; } }
測試類代碼:
public class DynaTest { public static void main(String[] args) { IHello hello = (IHello) new DynaProxyHello().bind(new Hello());//如果我們需要日志功能,則使用代理類 //IHello hello = new Hello();//如果我們不需要日志功能則使用目標(biāo)類 hello.sayHello("明天"); } }
看完上面的代碼可能和Spring AOP相比有一個問題,日志類只能在方法前后打印,但是AOP應(yīng)該是可以在滿足條件就可以執(zhí)行,所有是否可以將DynaPoxyHello對象和日志操作對象(Logger)解耦呢?
看下面代碼實(shí)現(xiàn),將將DynaPoxyHello對象和日志操作對象(Logger)解耦:
我們要在被代理對象的方法前面或者后面去加上日志操作代碼(或者是其它操作的代碼),那么,我們可以抽象出一個接口,這個接口里就只有兩個方法:一個是在被代理對象要執(zhí)行方法之前執(zhí)行的方法,我們?nèi)∶麨閟tart,第二個方法就是在被代理對象執(zhí)行方法之后執(zhí)行的方法,我們?nèi)∶麨閑nd。
Logger的接口:
public interface ILogger { void start(Method method); void end(Method method); }
Logger的接口實(shí)現(xiàn):
public class DLogger implements ILogger{ @Override public void start(Method method) { System.out.println(new Date()+ method.getName() + " say hello start..."); } @Override public void end(Method method) { System.out.println(new Date()+ method.getName() + " say hello end"); } }
動態(tài)代理類:
public class DynaProxyHello implements InvocationHandler{ //調(diào)用對象 private Object proxy; //目標(biāo)對象 private Object target; public Object bind(Object target,Object proxy){ this.target=target; this.proxy=proxy; return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; //反射得到操作者的實(shí)例 Class clazz = this.proxy.getClass(); //反射得到操作者的Start方法 Method start = clazz.getDeclaredMethod("start", new Class[]{Method.class}); //反射執(zhí)行start方法 start.invoke(this.proxy, new Object[]{this.proxy.getClass()}); //執(zhí)行要處理對象的原本方法 method.invoke(this.target, args); //反射得到操作者的end方法 Method end = clazz.getDeclaredMethod("end", new Class[]{Method.class}); //反射執(zhí)行end方法 end.invoke(this.proxy, new Object[]{method}); return result; } }
測試代碼:
public class DynaTest { public static void main(String[] args) { IHello hello = (IHello) new DynaProxyHello().bind(new Hello(),new DLogger());//如果我們需要日志功能,則使用代理類 //IHello hello = new Hello();//如果我們不需要日志功能則使用目標(biāo)類 hello.sayHello("明天"); } }
通過上面例子,可以發(fā)現(xiàn)通過動態(tài)代理和發(fā)射技術(shù),已經(jīng)基本實(shí)現(xiàn)了AOP的功能,如果我們只需要在方法執(zhí)行前打印日志,則可以不實(shí)現(xiàn)end()方法,這樣就可以控制打印的時機(jī)了。如果我們想讓指定的方法打印日志,我們只需要在invoke()方法中加一個對method名字的判斷,method的名字可以寫在xml文件中,這樣我們就可以實(shí)現(xiàn)以配置文件進(jìn)行解耦了,這樣我們就實(shí)現(xiàn)了一個簡單的spring aop框架。
以上內(nèi)容是小編給大家介紹的Spring 的aop實(shí)現(xiàn)原理 ,希望對大家有所幫助!
相關(guān)文章
Java實(shí)現(xiàn)通過IP獲取IP歸屬地的方法(離線+在線)
我們都知道安全攻擊都是在某臺客戶機(jī)上執(zhí)行某些惡意操作致使服務(wù)端響應(yīng)異常崩潰亦或響應(yīng)數(shù)據(jù)被篡改,首先我想到的是對訪問的web端做一個IP的校驗(yàn),那么我們首先得知道客戶端的IP是多少,接下來此文重點(diǎn)介紹如何獲得,需要的朋友可以參考下2023-10-10Java使用jni清屏功能的實(shí)現(xiàn)(只針對cmd)
JNI是Java Native Interface的縮寫,它提供了若干的API實(shí)現(xiàn)了Java和其他語言的通信(主要是C&C++)。這篇文章主要介紹了Java使用jni清屏功能的實(shí)現(xiàn)(只針對cmd) ,感興趣的朋友跟隨腳本之家小編一起學(xué)習(xí)吧2018-05-05Java實(shí)現(xiàn)在線考試系統(tǒng)與設(shè)計(jì)(學(xué)生功能)
這篇文章主要介紹了Java實(shí)現(xiàn)在線考試系統(tǒng)與設(shè)計(jì)(學(xué)生功能),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02使用springboot開發(fā)的第一個web入門程序的實(shí)現(xiàn)
這篇文章主要介紹了使用springboot開發(fā)的第一個web入門程序的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04springmvc+shiro自定義過濾器的實(shí)現(xiàn)代碼
這篇文章主要介紹了springmvc+shiro自定義過濾器的實(shí)現(xiàn)方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2018-10-10MyBatis與SpringMVC相結(jié)合實(shí)現(xiàn)文件上傳、下載功能
這篇文章主要介紹了MyBatis與SpringMVC相結(jié)合實(shí)現(xiàn)文件上傳、下載功能的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-06-06Java基于HttpClient實(shí)現(xiàn)RPC的示例
HttpClient可以實(shí)現(xiàn)使用Java代碼完成標(biāo)準(zhǔn)HTTP請求及響應(yīng)。本文主要介紹了Java基于HttpClient實(shí)現(xiàn)RPC,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10Springboot?上傳文件或頭像(MultipartFile、transferTo)
本文主要介紹了Springboot?上傳文件或頭像(MultipartFile、transferTo),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04