黑馬程序員_代理

----------- android培訓、java培訓、java學習型技術博客、期待與您交流! ------------

從生活中的例子引入代理:武漢人從武漢的代理商手中買聯想電腦和直接跑到北京傳智播客旁邊來找聯想總部買電腦,你覺得最終的主體業務目標有什麼區別嗎?基本上一樣吧,都解決了核心問題,但是,一點區別都沒有嗎?從代理商那裏買真的一點好處都沒有嗎?

答:批發進貨的成本和運輸費的優勢,比你自己直接到北京總部買的總成本要低吧。

l程序中的代理
要爲已存在的多個具有相同接口的目標類的各個方法增加一些系統功能,例如,異常處理、日誌、計算方法的運行時間、事務管理、等等,你準備如何做?
編寫一個與目標類具有相同接口的代理類,代理類的每個方法調用目標類的相同方法,並在調用方法時加上系統功能的代碼。(參看下頁的原理圖)
如果採用工廠模式和配置文件的方式進行管理,則不需要修改客戶端程序,在配置文件中配置是使用目標類、還是代理類,這樣以後很容易切換,譬如,想要日誌功能時就配置代理類,否則配置目標類,這樣,增加系統功能很容易,以後運行一段時間後,又想去掉系統功能也很容易。

還有一個十分典型的例子:大家玩遊戲,如果想要玩國外的服務器,或者是瀏覽國外的網站,由於國內的ip受限等原因,大家只能通過在網絡上尋找代理服務器來實現與國外的玩家互動。

代理類的優點:如果採用工廠模式和配置文件的方式進行管理如果想要日誌功能時,就配置代理類,否則配置目標類,增強了系統功能,方便切換。

代碼示例:

package cn.itcast.test3;
import java.lang.reflect.*;
import java.util.*;
public class ProxyTest {
	public static void main(String[] args) throws Exception{
//創建動態代理類的三種方式
		//方式一:通過接口的子類創建對象
		Collection proxy1 = (Collection)
				constructor.newInstance(new MyInvocationHandler());
		System.out.println(proxy1);//null
		System.out.println(proxy1.toString());//null
		proxy1.clear();//無異常
		//proxy1.size();//異常		
		//方式二:匿名內部類
		Collection proxy2 = (Collection)
				constructor.newInstance(new InvocationHandler(){
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						// TODO Auto-generated method stub
						return null;
					}
				});
		
		//方式三:
		//通過代理類的newProxyInstance方法直接創建對象
		Collection proxy3 = (Collection)Proxy.newProxyInstance(
			//定義代理類的類加載器
			Collection.class.getClassLoader(),
			//代理類要實現的接口列表
			new Class[]{Collection.class},
			//指派方法調用的調用處理程序
			new InvocationHandler() {
				//創建集合,制定一個目標
				ArrayList target = new ArrayList();
				public Object invoke(Object proxy, Method method, Object[] args)
						throws Throwable {
					//測試程序運行時間
					long beginTime = System.currentTimeMillis();
					//調用目標方法,將其從return抽出來,加入代理所需的代碼
					Object retVal = method.invoke(target, args);
					long endTime = System.currentTimeMillis();
					//測試
					System.out.println(method.getName() + 
							" run time of " + 
							(endTime - beginTime));
					return retVal;
				}
			}
			);
		//通過代理類調用目標方法,每調用一個目標的方法就會執行代理類的方法
		//當調用一次add方法時,就會找一次InvocationHandler這個參數的invoke方法
		proxy3.add("sdfd");
		proxy3.add("shrt");
		proxy3.add("rtbv");
		System.out.println(proxy3.size());
		System.out.println(proxy3.getClass().getName());
	}
}

系統中存在交叉業務,一個交叉業務就是要切入到系統中的一個方面,如下所示:
                              安全       事務         日誌
StudentService  ------|----------|------------|-------------
CourseService   ------|----------|------------|-------------
MiscService       ------|----------|------------|-------------
用具體的程序代碼描述交叉業務:
method1         method2          method3
{                      {                       { 
------------------------------------------------------切面
....            ....              ......
------------------------------------------------------切面
}                       }                       }
交叉業務的編程問題即爲面向方面的編程(Aspect oriented program ,簡稱AOP),AOP的目標就是要使交叉業務模塊化。可以採用將切面代碼移動到原始方法的周圍,這與直接在方法中編寫切面代碼的運行效果是一樣的,如下所示:
------------------------------------------------------切面
func1         func2            func3
{             {                { 
....            ....              ......
}             }                }
------------------------------------------------------切面
使用代理技術正好可以解決這種問題,代理是實現AOP功能的核心和關鍵技術


要爲系統中的各種接口的類增加代理功能,那將需要太多的代理類,全部採用靜態代理方式,將是一件非常麻煩的事情!寫成百上千個代理類,是不是太累!
JVM可以在運行期動態生成出類的字節碼,這種動態生成的類往往被用作代理類,即動態代理類。
JVM生成的動態類必須實現一個或多個接口,所以,JVM生成的動態類只能用作具有相同接口的目標類的代理。
CGLIB庫可以動態生成一個類的子類,一個類的子類也可以用作該類的代理,所以,如果要爲一個沒有實現接口的類生成動態代理類,那麼可以使用CGLIB庫。
代理類的各個方法中通常除了要調用目標的相應方法和對外返回目標返回的結果外,還可以在代理方法中的如下四個位置加上系統功能代碼:
1.在調用目標方法之前
2.在調用目標方法之後
3.在調用目標方法前後
4.在處理目標方法異常的catch塊中


創建實現了Collection接口的動態類和查看其名稱,分析Proxy.getProxyClass方法的各個參數。
 編碼列出動態類中的所有構造方法和參數簽名
 編碼列出動態類中的所有方法和參數簽名
 創建動態類的實例對象
 用反射獲得構造方法
 編寫一個最簡單的InvocationHandler類
 調用構造方法創建動態類的實例對象,並將編寫的InvocationHandler類的實例對象傳進去
 打印創建的對象和調用對象的沒有返回值的方法和getClass方法,演示調用其他有
 返回值的方法報告了異常。
將創建動態類的實例對象的代理改成匿名內部類的形式編寫,鍛鍊大家習慣匿名內部類。
總結思考:讓jvm創建動態類及其實例對象,需要給它提供哪些信息?
三個方面:
生成的類中有哪些方法,通過讓其實現哪些接口的方式進行告知;
產生的類字節碼必須有個一個關聯的類加載器對象;
生成的類中的方法的代碼是怎樣的,也得由我們提供。把我們的代碼寫在一個約定好了接口對象的方法中,把對象傳給它,它調用我的方法,即相當於插入了我的代碼。提供執行代碼的對象就是那個InvocationHandler對象,它是在創建動態類的實例對象的構造方法時傳遞進去的。在上面的InvocationHandler對象的invoke方法中加一點代碼,就可以看到這些代碼被調用運行了。用Proxy.newInstance方法直接一步就創建出代理對象。
代碼示例:

package cn.itcast.test3;
import java.lang.reflect.*;
import java.util.*;
public class ProxyTest {
	public static void main(String[] args) throws Exception{
//創建動態代理類的三種方式
		//方式一:通過接口的子類創建對象
		Collection proxy1 = (Collection)
				constructor.newInstance(new MyInvocationHandler());
		System.out.println(proxy1);//null
		System.out.println(proxy1.toString());//null
		proxy1.clear();//無異常
		//proxy1.size();//異常		
		//方式二:匿名內部類
		Collection proxy2 = (Collection)
				constructor.newInstance(new InvocationHandler(){
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						// TODO Auto-generated method stub
						return null;
					}
				});
		
		//方式三:
		//通過代理類的newProxyInstance方法直接創建對象
		Collection proxy3 = (Collection)Proxy.newProxyInstance(
			//定義代理類的類加載器
			Collection.class.getClassLoader(),
			//代理類要實現的接口列表
			new Class[]{Collection.class},
			//指派方法調用的調用處理程序
			new InvocationHandler() {
				//創建集合,制定一個目標
				ArrayList target = new ArrayList();
				public Object invoke(Object proxy, Method method, Object[] args)
						throws Throwable {
					//測試程序運行時間
					long beginTime = System.currentTimeMillis();
					//調用目標方法,將其從return抽出來,加入代理所需的代碼
					Object retVal = method.invoke(target, args);
					long endTime = System.currentTimeMillis();
					//測試
					System.out.println(method.getName() + 
							" run time of " + 
							(endTime - beginTime));
					return retVal;
				}
			}
			);
		//通過代理類調用目標方法,每調用一個目標的方法就會執行代理類的方法
		//當調用一次add方法時,就會找一次InvocationHandler這個參數的invoke方法
		proxy3.add("sdfd");
		proxy3.add("shrt");
		proxy3.add("rtbv");
		System.out.println(proxy3.size());
		System.out.println(proxy3.getClass().getName());
	}
}

動態生成的類實現了Collection接口(可以實現若干接口),生成的類有Collection接口中的所有方法和一個如下接受InvocationHandler參數的構造方法。構造方法接受一個InvocationHandler對象,接受對象了要幹什麼用呢?該方法內部的代碼會是怎樣的呢?實現Collection接口的動態類中的各個方法的代碼又是怎樣的呢?InvocationHandler接口中定義的invoke方法接受的三個參數又是什麼意思?
說明如下:

Client程序調用objProxy.add(“abc”)方法時,涉及三要素:objProxy對象、add方法、“abc”參數

ClassProxy$ {

  add(Object object) {

return handler.invoke(Object proxy, Method method, Object[] args);

  }

}

怎樣將目標傳進去:直接在InvocationHandler實現類中創建目標類的實例對象,可看運行效果和加入日誌代碼,但是毫無意義。爲InvocationHandler實現類注入目標的實例對象,不能採用匿名內部類的形式了。讓匿名內部類的InvocationHandler實現類訪問外面的方法中的目標類實例對象的final類型的引用變量。

動態代理的工作原理:Client(客戶端)調用代理,代理的構造方法接受一個InvocationHandler,client調用代理的各個方法,代理的各個方法請求轉發給剛纔通過構造方法傳入的handler對象,又把各請求分發給目標的相應的方法。就是將handler封裝起來,其中this引用了當前的放(發來什麼請求就接受哪個方法)。

將創建代理的過程改爲一種更優雅的方式,eclipse重構出一個getProxy方法綁定接受目標,同時返回代理對象,讓調用者更懶惰,更方便,調用者甚至不用接觸任何代理的API。

在這裏將InvocationHandler加入到Proxy的構造方法中,因此,在創建出來的對象,就會存有構造方法中InvocationHandler的一些功能和信息,因爲我們把想要運行的代碼封裝在InvocationHandler對象,把它傳入到構造函數中,那麼就實現了代理對象每次調用目標方法(因爲實現了同一接口)時,都會調用我們加入到InvocationHandler對象中的代碼。這就保證了每次調用代理時,可以在目標上加入我們自己加入的功能。

把系統功能代理模塊化,即切面代碼也改爲通過參數形式提供,怎麼把要執行的系統功能代碼以參數的形式提供:

把要執行的代碼裝到一個對象的某個方法中,然後把此對象作爲參數傳遞,接收者只要調用這個對象的方法,即等於執行了外接提供的代碼。

爲bind方法增加一個Advice參數。

代碼示例:

package cn.itcast.test3;
import java.lang.reflect.*;
import java.util.*;
public class MyProxy {
	public static void main(String[] args)throws Exception {
		//創建目標對象,並進行操作測試
		final ArrayList target = new ArrayList();
		Collection proxy = (Collection)getProxy(target,new MyAdvice());
		proxy.add("sdf");
		proxy.add("wgcd");
		proxy.add("hgwe");
		System.out.println(proxy.size());
		
	}
	//作爲一個通用的方法,就使用Object
	//傳入一個目標,並傳入一個接口,此接口作爲通信的契約,才能調用額外的方法
	private static Object getProxy(final Object target,final Advice advice) {
		Object proxy = Proxy.newProxyInstance(
				target.getClass().getClassLoader(),
				//這裏的接口要和target實現相同的接口
				target.getClass().getInterfaces(),
				new InvocationHandler() {
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						//通過契約,使用其方法--before和after方法
						advice.beforeMethod(method);
						Object value = method.invoke(target, args);
						advice.afterMethod(method);
						return value;
					}
				}
				);
		return proxy;
	}
}
//創建實現Advice接口的子類
package cn.itcast.test3;
import java.lang.reflect.Method;
//實現Advice接口中方法的具體內容
public class MyAdvice implements Advice {

	long beginTime = 0;
	public void beforeMethod(Method method) {
		// TODO Auto-generated method stub
		System.out.println("從這裏開始");
		beginTime = System.currentTimeMillis(); 
	}
	public void afterMethod(Method method) {
		// TODO Auto-generated method stub
		long endTime = System.currentTimeMillis();
		System.out.println("從這裏結束");
		System.out.println(method.getName() + " run time of " + (endTime-beginTime));
	}
}
//創建接口Advice
import java.lang.reflect.Method;
public interface Advice {
	void beforeMethod(Method method);
	void afterMethod(Method method);
}

----------- android培訓、java培訓、java學習型技術博客、期待與您交流! ------------

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章