代理模式

一、定義

    代理模式是一種很常見的設計模式。它使用代理對象完成用戶請求,屏蔽用戶對真是對象的訪問。就如同現實生活中的代理一樣,代理人被授權執行當事人的一些事宜,而無需當事人親自出面,從第三方的角度看,似乎當事人並不存在,因爲第三方只和代理人直接通信。

二、分類

    (1)靜態代理:

               由程序員創建或特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經存在了。

    (2)動態代理:

               在程序運行時,運用反射機制動態創建代理類。

三、目的

    在軟件設計中,使用代理的意圖很多,主要有三個:

    (1)安全原因:屏蔽第三方直接與真是對象進行通信。

    (2)RMI中:需要使用代理類處理遠程方法調用的技術細節。

    (3)提升系統性能,延遲加載。

      這裏重點講一下延遲加載是怎麼體現的。假設一個客戶端軟件,有根據用戶請求進行數據庫查詢的功能。那麼在查詢數據之前,必須得獲得數據庫連接。那麼這個獲取數據庫連接的操作應該放在什麼時候進行呢?假如放在軟甲開啓時,就去獲取數據庫的連接,有些人可能會說獲取一個連接而已,並不是什麼耗時的操作,沒有影響。但是,軟件並不是這麼簡單的,它有大量類似的初始化操作,比如xml的解析等等,單獨一個數據庫連接不耗時,但是大量的這種操作呢,如果都放在軟件啓動時,那麼勢必會影響軟件的性能和啓動速度。因此,我們引入了代理模式,使用代理對當事人進行封裝,當系統啓動時,僅僅初始化代理類,並不進行數據庫連接等初始化操作,僅當進行真正的數據庫查詢操作時,纔會去獲取數據庫連接。
    在系統啓動時,將消耗資源最多的方法都使用代理模式分離,就可以加快系統的啓動速度,減少用戶的等待時間。而在用戶真正做查詢操作時,再由代理類,單獨去加載真實的數據庫查詢類,完成用戶請求。這裏說的可能比較抽象,下面會舉例說明。

四、四個角色

    (1)主題接口:定義代理類和真實主題的公共接口

    (2)真實主題:真正實現業務邏輯的類

    (3)代理類:用來代理和封裝真實主題

    (4)Main:客戶端,第三方,與代理類進行直接通信,完成一些工作。

五、實現

    (1)靜態代理:

/*
 * 靜態代理
 * 每一個代理類只能爲一個接口服務,這樣一來程序開發中必然會產生過多的代理
 */
public class LearnProxy0 {
	public static void main(String[] args) {
		IDBQuery dbQuery=new DBQueryProxy();
		dbQuery.select();

	}
}
interface IDBQuery{
	public void select();
}
class DBQuery implements IDBQuery{
	public  DBQuery() {
		try {
			//可能包含數據庫連接等耗時操作,這裏僅僅是模擬
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	@Override
	public void select() {
		// TODO Auto-generated method stub
		System.out.println("進行數據庫查詢操作...");
	}
}
class DBQueryProxy implements IDBQuery{
	//初始值設置爲空,即在軟件啓動時,不進行初始化操作
	private IDBQuery dbQuery=null;  

	@Override
	public void select() {
		// TODO Auto-generated method stub
		//只有在真正需要的時候,才創建真正的對象
		if (dbQuery==null) {
			dbQuery=new DBQuery();
		}
		System.out.println("查詢操作前...");
		dbQuery.select();
		System.out.println("查詢操作後...");
	}
}


    (2)JDK動態代理:

/*
 * JDK的動態代理
 */
public class LearnProxy1 {

	public static void main(String[] args) {
		DynamicProxy proxy=new DynamicProxy();
		Sport sportProxy=(Sport)proxy.bind(new Basketball());
		sportProxy.play();
	}
}

interface Sport{
	public void play();
}

class Basketball implements Sport{

	public void play() {
		System.out.println("i am playing basketball");
	}
	
}
class DynamicProxy implements InvocationHandler{
	//要代理的接口,因此動態代理可以代理多個不同接口
	private Object target;
	public Object bind(Object target){  //設置目標代理對象,並且返回代理
		this.target=target;
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), //注意第二個參數是接口的列表,代理實例實現了所有接口
				target.getClass().getInterfaces(), this);        //因此,我們可以像使用接口的方式一樣使用代理來調用方法
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("方法執行前");
		method.invoke(target, args);    //通過反射機制調用真實主題的目標方法
		System.out.println("方法執行後");
		return null;
	}
}


    (3)cglib動態代理:

/*
 * JDK的動態代理依靠接口實現,如果有些類並沒有實現接口,則不能使用JDK代理,這就要使用cglib動態代理了
 * 
 * cglib是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因爲採用的
 * 是繼承,所以不能對final修飾的類進行代理
 */
public class LearnProxy2 implements MethodInterceptor{
	//
	private Object target;
	//設置被代理的對象,並且返回一個動態代理
	public Object bind(Object target){
		this.target=target;
		Enhancer enhancer=new Enhancer();
		enhancer.setSuperclass(this.target.getClass());
		enhancer.setCallback((Callback) this);	
		return enhancer.create();
	}


	@Override
	public Object intercept(Object target, Method method, Object[] arg2,
			MethodProxy proxy) throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("方法執行前...");
		proxy.invokeSuper(target, arg2);
		System.out.println("方法執行後");
		return null;
	}
	public static void main(String[] args) {
		LearnProxy2 proxy=new LearnProxy2();
		Actor actorProxy=(Actor)proxy.bind(new Actor());
		actorProxy.play();
	}
}

class Actor{
	public void play() {
		System.out.println("表演....");
	}
}


    使用JDK創建代理有一個限制,即他只能爲接口創建代理實例。但是現實情況是,有的類並沒有實現接口(現實中存在一些並沒有使用接口的項目),但是它也需要動態代理,那怎麼辦呢?這個時候就需要使用CGLIB啦。

    CGLIB採用非常底層的字節碼技術,可以爲一個類創建子類,並在子類中採用方法攔截的技術攔截所有父類方法的調用,並且順勢織入橫切邏輯。

    另外,CGLIB創建的動態代理的性能要比JDK創建的代理的性能好很多。但是,CGLIB創建代理對象花費的時間要比JDK多。因此,我們建議,對於singleton的代理對象,因爲不需要頻繁創建,最好使用CGLIB技術創建代理,反之,適合使用JDK代理技術。還要注意的是,由於CGLIB採用動態創建子類的方式生成代理對象,所以不能對目標類中的final和private方法進行代理。

    下面篇,我會講解一下動態代理在AOP中的應用。

六、上述實現方法存在的問題

(1)我們爲所有的方法都添加了橫切邏輯,但是,有時候這並不是我們想要的,我們只希望對業務類的某幾個特定方法加上橫切邏輯。
(2)我們通過硬編碼的方法指定了織入橫切邏輯的織入點,即在業務方法的執行前和執行後織入了代碼,不具有靈活性。
    這幾個問題都會在Spring AOP中得到解決。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章