代理和AOP
代理和AOP一.起源有時,我們在寫一些功能方法的時候,需要加上特定的功能.比如說在方法調用的前后加上日志的操作,或者是事務的開啟與關閉.對于一個方法來說,很簡單,只要在需要的地方增加一些代碼就OK.但是如果有很多方法都需要增加這種特定的操作呢?
沒錯,將這些特定的代碼抽象出來,并且提供一個接口供調用者使用:
- public class RecordLog
- {
- public static void recordLog()
- {
- // 記錄日志的操作
- System.out.println("記錄日志...");
- }
- }
那么在其他的方法中,就可以使用RecordLog.recordLog()方法了.但你會發(fā)現(xiàn),這仍不是個好的設計,因為在我們的代碼里到處充塞著
RecordLog.recordLog()這樣的語句:
- public class A
- {
- public void a()
- {
- // 1.記錄日志
- RecordLog.recordLog();
- // 2.類A的方法a的操作
- }
- }
- public class B
- {
- public void b()
- {
- // 1.記錄日志
- RecordLog.recordLog();
- // 2.類B的方法b的操作
- }
- }
- ......
這樣雖然會在一定程度減輕代碼量,但你會發(fā)現(xiàn),仍有大量的地方有重復的代碼出現(xiàn)!這絕對不是優(yōu)雅的寫法!
為了避免這種吃力不討好的現(xiàn)象發(fā)生,“代理”粉墨登場了.
二.傳統(tǒng)的代理.靜態(tài)的代理.面向接口編程
同樣為了實現(xiàn)以上的功能,我們在設計的時候做了個小小的改動.
2.1 抽象出來的記錄日志的類:
- public class RecordLog
- {
- public static void recordLog()
- {
- // 記錄日志的操作
- System.out.println("記錄日志...");
- }
- }
2.2 設計了一個接口:
- public interface PeopleInfo
- {
- public void getInfo();
- }
該接口只提供了待實現(xiàn)的方法.
2.3 實現(xiàn)該接口的類:
- public class PeopleInfoImpl implements PeopleInfo
- {
- private String name;
- private int age;
- // 構造函數(shù)
- public PeopleInfoImpl(String name, int age)
- {
- this.name = name;
- this.age = age;
- }
- public void getInfo()
- {
- // 方法的具體實現(xiàn)
- System.out.println("我是" + name + ",今年" + age + "歲了.");
- }
- }
這個類僅僅是實現(xiàn)了PeopleInfo接口而已.平平實實.好了.關鍵的地方來了.就在下面!
2.4 創(chuàng)建一個代理類:
- public class PeopleInfoProxy implements PeopleInfo
- {
- // 接口的引用
- private PeopleInfo peopleInfo;
- // 構造函數(shù) .針對接口編程,而非針對具體類
- public RecordLogProxy(PeopleInfo peopleInfo)
- {
- this.peopleInfo = peopleInfo;
- }
- // 實現(xiàn)接口中的方法
- public void record()
- {
- // 1.記錄日志
- RecordLog.recordLog();
- // 2.方法的具體實現(xiàn)
- peopleInfo.getInfo();
- }
- }
這個是類是一個代理類,它同樣實現(xiàn)了PeopleInfo接口.比較特殊的地方在于這個類中有一個接口的引用private PeopleInfo peopleInfo;通過
這個引用,可以調用實現(xiàn)了該接口的類的實例的方法!
而不管是誰,只要實現(xiàn)了PeopleInfo這個接口,都可以被這個引用所引用.也就是說,這個代理類可以代理任何實現(xiàn)了接口的PeopleInfo的類.具體
如何實現(xiàn),請看下面:
2.5 Main
- public class Main
- {
- public static void main(String[] args)
- {
- // new了一個對象
- PeopleInfoImpl peopleInfoImpl = new PeopleInfoImpl("Rock",24);
- // 代理該對象
- PeopleInfoProxy peopleInfoProxy = new PeopleInfoProxy(PeopleInfoImpl);
- // 調用代理類的方法.輸入的是目標類(即被代理類的方法的實現(xiàn))
- peopleInfoProxy.getInfo();
- }
- }
這樣,輸出的結果將是:
記錄日志...
我是Rock,今年24歲了.
由這個例子可見,這么做了之后不但省略了很多代碼,而且不必要知道具體是由哪個類來執(zhí)行方法.只需實現(xiàn)了特定的接口,代理類就可以打點一切
了.這就是面向接口的威力!HOHO...
三.動態(tài)代理.Java的動態(tài)機制.
面向接口的編程確實讓我們省了不少心,只要實現(xiàn)一個特定的接口,就可以處理很多的相關的類了.
不過,這總是要實現(xiàn)一個“特定”的接口,如果有很多很多這樣的接口需要被實現(xiàn)...也是件比較麻煩的事情.
好在,JDK1.3起,就有了動態(tài)代理機制,主要有以下兩個類和一個接口:
- java.lang.reflect.Proxy
- java.lang.reflect.Method
- java.lang.reflect.InvocationHandler
所謂動態(tài)代理,就是JVM在內存中動態(tài)的構造代理類.說的真是玄,還是看看代碼吧.
3.1 抽象出來的記錄日志的類:
- public class RecordLog
- {
- public static void recordLog()
- {
- // 記錄日志的操作
- System.out.println("記錄日志...");
- }
- }
3.2 設計了一個接口:
- public interface PeopleInfo
- {
- public void getInfo();
- }
該接口只提供了待實現(xiàn)的方法.
3.3 實現(xiàn)該接口的類:
- public class PeopleInfoImpl implements PeopleInfo
- {
- private String name;
- private int age;
- // 構造函數(shù)
- public PeopleInfoImpl(String name, int age)
- {
- this.name = name;
- this.age = age;
- }
- public void getInfo()
- {
- // 方法的具體實現(xiàn)
- System.out.println("我是" + name + ",今年" + age + "歲了.");
- }
- }
一直到這里,都和第二節(jié)沒區(qū)別,好嘛,下面就是關鍵喲.
3.4 創(chuàng)建一個代理類,實現(xiàn)了接口InvocationHandler:
- public class PeopleInfoProxy implements InvocationHandler
- {
- // 定義需要被代理的目標對象
- private Object target;
- // 將目標對象與代理對象綁定
- public Object bind(Object targer)
- {
- this.target = target;
- // 調用Proxy的newProxyInstance方法產(chǎn)生代理類實例
- return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
- }
- // 實現(xiàn)接口InvocationHandler的invoke方法
- // 該方法將在目標類的被代理方法被調用之前,自動觸發(fā)
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
- {
- Object result = null;
- // 1.目標類的被代理方法被調用之前,可以做的操作
- RecordLog.recordLog();
- // 2.方法的具體實現(xiàn)
- result = method.invoke(target, args);
- // 3.還可以在方法調用之后加上的操作
- // 自己補充
- return result;
- }
- }
關于Proxy, Method, InvocationHandler的具體說明,請參見JDK_API.
只對代碼中關鍵部分做些解釋說明:
3.4.1
- Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
表示生成目標類的代理類,傳入的參數(shù)有目標類的ClassLoader, 目標類的接口列表, 和實現(xiàn)了接口InvocationHandler的代理類.
這樣,bind方法就得到了目標類的代理類.
3.4.2
- method.invoke(target, args);
目標類的被代理方法在被代用前,會自動調用InvocationHandler接口的invoke方法.
在該方法中,我們可以對目標類的被代理方法進行加強,比如說在其前后加上事務的開啟和關閉等等.
這段代碼才是真正調用目標類的被代理方法.
就這樣,我們不用實現(xiàn)其他任何的接口,理論上就能代理所有類了.調用的方式如下:
3.5 Main:
- public class Main
- {
- public static void main(String[] args)
- {
- PeopleInfo peopleInfo = null;
- PeopleInfoProxy peopleInfoProxy = new PeopleInfoProxy();
- // 傳入的參數(shù)是目標類實例,生成代理類實例,類型為Object
- Object obj = peopleInfoProxy.bind(new PeopleInfoImpl("Rock", 24));
- if(obj instanceof PeopleInfo)
- {
- peopleInfo = (PeopleInfo)obj;
- }
- peopleInfo.getInfo();
- }
- }
執(zhí)行結果和上一節(jié)一樣.
這就是使用Java動態(tài)代理機制的基本概述.而下一節(jié),將要把Dynamic Proxy(動態(tài)代理)和AOP聯(lián)系起來.
四.AOP概述.Spring的AOP.
AOP(Aspect Oriented Programming)面向切面編程.是一種比較新穎的設計思想.是對OOP(Object Orientd Programming)面向對象編程的一種有益的補充.
4.1 OOP和AOP
OOP對業(yè)務處理過程中的實體及其屬性和行為進行了抽象封裝,以獲得更加清晰高效果的邏輯劃分.研究的是一種“靜態(tài)的”領域.
AOP則是針對業(yè)務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段.研究的是一種“動態(tài)的”領域.
舉例說,某個網(wǎng)站(5016?)用戶User類又可分為好幾種,區(qū)長,管理員,斑竹和普通水友.我們把這些會員的特性進行提取進行封裝,這是OOP.
而某一天,區(qū)長開會了,召集斑竹等級以上的會員參與,這樣,普通水友就不能訪問相關資源.
我們怎么做到讓普通水友訪問不了資源,而斑竹等級以上會員可以訪問呢.
權限控制.對,權限.當水友們進行操作的時候,我們給他的身份進行權限的判斷.
請注意,當且僅需水友門執(zhí)行了操作的時候,我們才需要進行權限判斷,也就是說,這是發(fā)生在一個業(yè)務處理的過程中的一個片面.
我們對這一個片面進行編程,就是AOP!
我這樣,你應該能理解吧.
4.2 AOP的基本術語
4.2.1 切面Aspect
業(yè)務處理過程中的一個截面.就像權限檢查.
通過切面,可以將不同層面的問題隔離開:瀏覽帖子和權限檢查兩者互不相干.
這樣一來,也就降低了耦合性,我們可以把注意力集中到各自的領域中.
上兩節(jié)的例子中,getInfo()和recordLog()就是兩個領域的方法,應該處于切面的不同端.哎呀,不知不覺間,我們就用了AOP.呵呵...
4.2.2 連接點JoinPoint
程序運行中的某個階段點.如某個方法的調用,或者異常的拋出等.
在前面,我們總是在getInfo()的前后加了recordLog()等操作,這個調用getInfo()就是連接點.
4.2.3 處理邏輯Advice
在某個連接點采取的邏輯.
這里的邏輯有三種:
I. Around 在連接點前后插入預處理和后處理過程.
II. Before 在連接點前插入預處理過程.
III.Throw 在連接點拋出異常的時候進行異常處理.
4.2.4 切點PointCut
一系列連接點的集合,它指明處理邏輯Advice將在何在被觸發(fā).
4.3 Spring中的AOP
Spring提供內置AOP支持.是基于動態(tài)AOP機制的實現(xiàn).
所謂動態(tài)AOP,其實就是動態(tài)Proxy模式,在目標對象的方法前后插入相應的代碼.(比如說在getInfo()前后插入的recordLog())
Spring AOP中的動態(tài)Proxy模式,是基于Java Dynamic Proxy(面向Interface)和CGLib(面向Class)的實現(xiàn).
為什么要分面向接口和面向類呢.
還記得我們在生成代理類的代碼嗎:
- Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
這里面的參數(shù)不許為空,也就是說:obj.getClass().getInterfaces()必有值,即目標類一定要實現(xiàn)某個接口.
有了這些,JVM在內存中就動態(tài)的構造出代理出來.
而沒有實現(xiàn)任何接口的類,就必須使用CGLib來動態(tài)構造代理類.值得一提的是,CGLib構造的代理類是目標類的一個子類.
4.4 相關工程簡解
Spring的相關知識不應該在這里講,難度系數(shù)過大.這里只給個簡單例子.供參考.
4.4.1 準備工作
打開Eclipse.新建Java工程,取名為AOP_Proxy.完成.
復制spring-2.0.jar.粘貼到AOP_Proxy下.
右擊AOP_Proxy-->屬性-->Java構建路徑-->庫-->添加JAR-->找spring-2.0.jar-->添加確定.
復制commons-logging.jar.粘貼到AOP_Proxy下.
右擊AOP_Proxy-->屬性-->Java構建路徑-->庫-->添加JAR-->找commons-logging.jar-->添加確定.
4.4.2 寫代碼
代碼略.配置文件略.
4.4.3 導入工程的步驟
新建工程AOP_Pro/Files/qileilove/AOP_Proxy.rarxy-->完成-->右擊AOP_Proxy-->導入-->常規(guī)-->文件系統(tǒng)-->找到項目文件,導入完成.
兩個jar包和項目文件(項目文件需要先解壓).