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