代理和AOP(轉帖)

 一.起源
有時,我們在寫一些功能方法的時候,需要加上特定的功能.比如說在方法調用的前後加上日誌的操作,或者是事務的開啓與關閉.對於一個方法來說,很簡單,只要在需要的地方增加一些代碼就OK.但是如果有很多方法都需要增加這種特定的操作呢?

沒錯,將這些特定的代碼抽象出來,並且提供一個接口供調用者使用:
Java代碼 複製代碼
  1. public class RecordLog   
  2. {   
  3.     public static void recordLog()   
  4.     {   
  5.         //  記錄日誌的操作   
  6.         System.out.println("記錄日誌...");   
  7.     }   
  8. }  
	public class RecordLog
	{
		public static void recordLog()
		{
			//	記錄日誌的操作
			System.out.println("記錄日誌...");
		}
	}

那麼在其他的方法中,就可以使用RecordLog.recordLog()方法了.但你會發現,這仍不是個好的設計,因爲在我們的代碼裏到處充塞着
RecordLog.recordLog()這樣的語句:
Java代碼 複製代碼
  1. public class A   
  2. {   
  3.     public void a()   
  4.     {   
  5.         //  1.記錄日誌   
  6.         RecordLog.recordLog();   
  7.   
  8.         //  2.類A的方法a的操作   
  9.     }   
  10. }   
  11. public class B   
  12. {   
  13.     public void b()   
  14.     {   
  15.         //  1.記錄日誌   
  16.         RecordLog.recordLog();   
  17.   
  18.         //  2.類B的方法b的操作   
  19.     }   
  20. }   
  21. ......  
 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 抽象出來的記錄日誌的類:
Java代碼 複製代碼
  1. public class RecordLog   
  2. {   
  3.     public static void recordLog()   
  4.     {   
  5.         //  記錄日誌的操作   
  6.         System.out.println("記錄日誌...");   
  7.     }   
  8. }  
	public class RecordLog
	{
		public static void recordLog()
		{
			//	記錄日誌的操作
			System.out.println("記錄日誌...");
		}
	}

2.2 設計了一個接口:
Java代碼 複製代碼
  1. public interface PeopleInfo   
  2. {   
  3.     public void getInfo();   
  4. }  
public interface PeopleInfo
{
	public void getInfo();
}

該接口只提供了待實現的方法.

2.3 實現該接口的類:
Java代碼 複製代碼
  1. public class PeopleInfoImpl implements PeopleInfo   
  2. {      
  3.     private String name;   
  4.   
  5.     private int age;   
  6.   
  7.     //  構造函數   
  8.     public PeopleInfoImpl(String name, int age)   
  9.     {   
  10.         this.name = name;   
  11.         this.age  = age;   
  12.     }   
  13.   
  14.     public void getInfo()   
  15.     {   
  16.         //  方法的具體實現   
  17.         System.out.println("我是" + name + ",今年" + age + "歲了.");   
  18.     }   
  19. }  
	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 創建一個代理類:
Java代碼 複製代碼
  1. public class PeopleInfoProxy implements PeopleInfo   
  2. {   
  3.     //  接口的引用   
  4.     private PeopleInfo peopleInfo;   
  5.   
  6.     //  構造函數    .針對接口編程,而非針對具體類   
  7.     public RecordLogProxy(PeopleInfo peopleInfo)   
  8.     {   
  9.         this.peopleInfo = peopleInfo;   
  10.     }   
  11.   
  12.     //  實現接口中的方法   
  13.     public void record()   
  14.     {   
  15.         //  1.記錄日誌   
  16.         RecordLog.recordLog();   
  17.   
  18.         //  2.方法的具體實現   
  19.         peopleInfo.getInfo();   
  20.     }   
  21. }  
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
Java代碼 複製代碼
  1. public class Main   
  2. {   
  3.     public static void main(String[] args)   
  4.     {   
  5.         //  new了一個對象   
  6.         PeopleInfoImpl peopleInfoImpl = new PeopleInfoImpl("Rock",24);   
  7.   
  8.         //  代理該對象   
  9.         PeopleInfoProxy peopleInfoProxy = new PeopleInfoProxy(PeopleInfoImpl);   
  10.   
  11.         //  調用代理類的方法.輸入的是目標類(即被代理類的方法的實現)   
  12.         peopleInfoProxy.getInfo();   
  13.     }   
  14. }  
	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代碼 複製代碼
  1. java.lang.reflect.Proxy   
  2. java.lang.reflect.Method   
  3. java.lang.reflect.InvocationHandler  
java.lang.reflect.Proxy
java.lang.reflect.Method
java.lang.reflect.InvocationHandler

所謂動態代理,就是JVM在內存中動態的構造代理類.說的真是玄,還是看看代碼吧.

3.1 抽象出來的記錄日誌的類:
Java代碼 複製代碼
  1. public class RecordLog   
  2. {   
  3.     public static void recordLog()   
  4.     {   
  5.         //  記錄日誌的操作   
  6.         System.out.println("記錄日誌...");   
  7.     }   
  8. }  
	public class RecordLog
	{
		public static void recordLog()
		{
			//	記錄日誌的操作
			System.out.println("記錄日誌...");
		}
	}

3.2 設計了一個接口:
Java代碼 複製代碼
  1. public interface PeopleInfo   
  2. {   
  3.     public void getInfo();   
  4. }  
	public interface PeopleInfo
	{
		public void getInfo();
	}

該接口只提供了待實現的方法.

3.3 實現該接口的類:
Java代碼 複製代碼
  1. public class PeopleInfoImpl implements PeopleInfo   
  2. {      
  3.     private String name;   
  4.   
  5.     private int age;   
  6.   
  7.     //  構造函數   
  8.     public PeopleInfoImpl(String name, int age)   
  9.     {   
  10.         this.name = name;   
  11.         this.age  = age;   
  12.     }   
  13.   
  14.     public void getInfo()   
  15.     {   
  16.         //  方法的具體實現   
  17.         System.out.println("我是" + name + ",今年" + age + "歲了.");   
  18.     }   
  19. }  
	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:
Java代碼 複製代碼
  1. public class PeopleInfoProxy implements InvocationHandler   
  2. {   
  3.     //  定義需要被代理的目標對象   
  4.     private Object target;   
  5.   
  6.     //  將目標對象與代理對象綁定   
  7.     public Object bind(Object targer)   
  8.     {   
  9.         this.target = target;   
  10.   
  11.         //  調用Proxy的newProxyInstance方法產生代理類實例   
  12.         return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);   
  13.     }   
  14.   
  15.     //  實現接口InvocationHandler的invoke方法   
  16.     //  該方法將在目標類的被代理方法被調用之前,自動觸發   
  17.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable   
  18.     {   
  19.         Object result = null;   
  20.            
  21.         //  1.目標類的被代理方法被調用之前,可以做的操作   
  22.         RecordLog.recordLog();   
  23.   
  24.         //  2.方法的具體實現   
  25.         result = method.invoke(target, args);   
  26.   
  27.         //  3.還可以在方法調用之後加上的操作   
  28.         //  自己補充   
  29.   
  30.         return result;   
  31.     }   
  32. }  
	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
Java代碼 複製代碼
  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
Java代碼 複製代碼
  1. method.invoke(target, args);  
method.invoke(target, args);

目標類的被代理方法在被代用前,會自動調用InvocationHandler接口的invoke方法.
在該方法中,我們可以對目標類的被代理方法進行加強,比如說在其前後加上事務的開啓和關閉等等.
這段代碼纔是真正調用目標類的被代理方法.

就這樣,我們不用實現其他任何的接口,理論上就能代理所有類了.調用的方式如下:

3.5 Main:
Java代碼 複製代碼
  1. public class Main   
  2. {   
  3.     public static void main(String[] args)   
  4.     {   
  5.         PeopleInfo peopleInfo = null;   
  6.   
  7.         PeopleInfoProxy peopleInfoProxy = new PeopleInfoProxy();   
  8.   
  9.         //  傳入的參數是目標類實例,生成代理類實例,類型爲Object   
  10.         Object obj = peopleInfoProxy.bind(new PeopleInfoImpl("Rock"24));   
  11.   
  12.         if(obj instanceof PeopleInfo)   
  13.         {   
  14.             peopleInfo = (PeopleInfo)obj;   
  15.         }   
  16.         peopleInfo.getInfo();   
  17.     }   
  18. }  
	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)的實現.

爲什麼要分面向接口和麪向類呢.
還記得我們在生成代理類的代碼嗎:
Java代碼 複製代碼
  1. 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-->導入-->常規-->文件系統-->找到項目文件,導入完成.

發佈了30 篇原創文章 · 獲贊 1 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章