JDK中的動態代理機制

定義

代理模式:爲其餘對象提供一種代理以控制對這個對象的訪問。程序員

事例

小明是一個程序員,在公司負責項目的研發工做。有一天,客戶打電話進來,溝通以後,原來客戶是有個模塊需求要變更一下。小明卻沒有應允,而是讓客戶去找產品經理老王溝通。bash

是小明偷懶不想幹活嗎?顯然不是。咱們把這個事例對應到上面的定義上,程序員小明能夠映射爲其餘對象,產品經理老王是小明的代理。它來控制小明這個對象的訪問。app

代理模式.png

咱們看上面的類圖,能夠簡單概括如下角色。函數

  • Subject 某一類主題。 好比工程師
  • Proxy 代理對象。 好比產品經理老王
  • RealSubject 真實對象。 好比程序員小明

靜態代理

咱們經過代碼來重現上面的場景。測試

首先定義一個 工程師的接口,它有一個編碼的方法。ui

public interface Engineer {
	
	void coding();
}
複製代碼

小明是個Java碼農,啊呸。是個Java工程師,要實現工程師這個接口。this

public class JavaEngineer implements Engineer{

	private String name;
	
	public JavaEngineer(String name){
		this.name = name;
	}
	
	public void coding() {
		
		System.out.println(name+" :正在努力的coding...");
		System.out.println(name+" :終於改完了!");
	}
}
複製代碼

產品經理老王也讓他實現工程師的接口。他代理了公司裏的Java工程師對象,當客戶有需求提出來,他要整理評估一下,當須要coding的時候,他直接轉交給具體的工程師去處理。編碼

public class ProductManager implements Engineer{

	private String name;
	private JavaEngineer engineer;
	
	public ProductManager(JavaEngineer engineer,String name){
		this.engineer = engineer;
		this.name = name;
	}
	
	public void coding() {
		arrange();
		engineer.coding();
		appease();
	}
	
	
	private void arrange(){
		System.out.println(this.name+":整理客戶需求中...");
		System.out.println(this.name+":輸出需求文檔,交給碼農去完成!");
	}

	private void appease(){
		System.out.println(this.name+":哎,需求變好屢次了.得安撫一下這個碼農纔好!");
	}
}
複製代碼

咱們來重現一下這個場景。spa

//有一個美麗的Java工程師,他的名字叫小明。
JavaEngineer engineer = new JavaEngineer("XiaoMing");

//一樣,還有一個猥瑣的老王。
ProductManager manager = new ProductManager(engineer,"老王");

//有新需求的時候,老王負責去溝通搞定。
manager.coding();

System.out.println("------------------輸出結果-----------------------");
//老王:整理客戶需求中...
//老王:輸出需求文檔,交給碼農去完成!
//XiaoMing :正在努力的coding...
//XiaoMing :終於改完了!
//老王:哎,需求變好屢次了.得安撫一下這個碼農纔好!
複製代碼

動態代理

JDK經過反射機制給咱們提供了動態代理的實現,容許開發人員在運行時刻動態的建立出代理類及其對象。當使用者調用了代理對象所代理的接口中的方法的時候,這個調用的信息會被傳遞給InvocationHandler的invoke方法。在 invoke方法的參數中能夠獲取到代理對象、方法對應的Method對象和調用的實際參數。invoke方法的返回值被返回給使用者。這種作法實際上至關於對方法調用進行了攔截。代理

關鍵有兩個類,Proxy和InvocationHandler 。

  • Proxy 用於生成代理類
  • InvocationHandler 用於調用目標類的方法,而且容許在調用先後插入其餘的邏輯

上面的事例咱們改爲動態代理方式來看一下。先定義一個調用處理程序

public class ProxyHandler implements InvocationHandler{

	private Object target;
	private String name;
	
	public ProxyHandler(Object target,String name){
		this.target = target;
		this.name = name;
	}
	
	
	public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
		arrange();
		method.invoke(target, args);
		appease();
		return null;
	}

	
	private void arrange(){
		System.out.println(this.name+":整理客戶需求中...");
		System.out.println(this.name+":輸出需求文檔,交給工程師去完成!");
	}

	private void appease(){
		System.out.println(this.name+":哎,需求變好屢次了.得安撫一下這個碼農纔好!");
	}
}
複製代碼

而後咱們來測試一下

//有一個美麗的Java工程師,他的名字叫小明。
Engineer engineer = new JavaEngineer("XiaoMing");

//代理實例的調用處理程序
ProxyHandler handler = new ProxyHandler(engineer, "老王");

//返回指定接口的代理類實例,該接口能夠將方法指派到指定的handler
Engineer proxy = (Engineer) Proxy.newProxyInstance(Engineer.class.getClassLoader(), 
			new Class[]{Engineer.class}, handler);

proxy.coding();

System.out.println("------------------輸出結果-----------------------");
//老王:整理客戶需求中...
//老王:輸出需求文檔,交給碼農去完成!
//XiaoMing :正在努力的coding...
//XiaoMing :終於改完了!
//老王:哎,需求變好屢次了.得安撫一下這個碼農纔好!
複製代碼

內存中的代理類實例長啥樣?

咱們看到,Proxy類經過靜態方法newProxyInstance就生成了一個代理類的實例。先無論它是怎麼樣生成的,可是我想關心它到底長什麼樣子呢?把它拿出來看看。 JDK生成的代理類以$Porxy開頭,後面跟一個從0開始的自增加數字。好比,$Proxy0,經過下面這段代碼,能夠將代理類實例輸出到$Proxy0.class文件中。

byte[] data = ProxyGenerator.generateProxyClass("$Proxy0",new Class[] {Engineer.class});	
FileOutputStream fileOutputStream = new FileOutputStream("$Proxy0.class");
fileOutputStream.write(data);
fileOutputStream.close();
複製代碼

經過反編譯class文件,獲得代理類刪減整理以下:

public class $Proxy0 extends Proxy implements Engineer {

	private static final long serialVersionUID = 1L;
	private static Method m3;

	protected $Proxy0(InvocationHandler h) {
		//經過構造方法 把代理實例的調用處理程序傳進來
		super(h);
	}
	public final void coding() {
		try {
			//此處的h是父類Proxy的屬性,對應的就是ProxyHandler
			//invoke就至關於ProxyHandler.invoke(this, m3, null);
			this.h.invoke(this, m3, null);
			return;
		} catch (RuntimeException localRuntimeException) {
			throw localRuntimeException;
		} catch (Throwable localThrowable) {
			throw new UndeclaredThrowableException(localThrowable);
		}
	}
	static {
		try {
			//經過反射拿到指定接口的方法
			m3 = Class.forName("proxy.proxy2.Engineer").getMethod("coding",
					new Class[0]);
		} catch (NoSuchMethodException localNoSuchMethodException) {
			throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
		} catch (ClassNotFoundException localClassNotFoundException) {
			throw new NoClassDefFoundError(
					localClassNotFoundException.getMessage());
		}
	}
}
複製代碼

若是咱們把經過反編譯獲得的class文件寫成一個Java類,調用它一樣能夠實現代理功能。

Engineer engineer = new JavaEngineer("XiaoMing");
ProxyHandler handler = new ProxyHandler(engineer, "老王");
$Proxy0 p0 = new $Proxy0(handler);
p0.coding();
System.out.println("------------------輸出結果-----------------------");
//老王:整理客戶需求中...
//老王:輸出需求文檔,交給碼農去完成!
//XiaoMing :正在努力的coding...
//XiaoMing :終於改完了!
//老王:哎,需求變好屢次了.得安撫一下這個碼農纔好!
複製代碼

總結

關於動態代理建立對象的過程,咱們大概能夠這樣總結一下。

  • 一、經過實現InvocationHandler接口建立本身的調用處理器
  • 二、經過爲Proxy類指定ClassLoader對象和一組interface建立動態代理類
  • 三、經過反射機制獲取動態代理類的構造函數,其參數類型是調用處理器接口類型
  • 四、經過構造函數建立代理類實例,此時需將調用處理器對象做爲參數被傳入

Proxy類的newProxyInstance方法封裝了2-4,只需2步就完成了代理對象的建立。 生成的代理對象集成Proxy,實現被代理對象接口。被代理對象接口的方法實際調用處理器的invoke方法,而處理器的invoke方法利用反射調用的是被代理對象的的方法method.invoke(target,args)。

相關文章
相關標籤/搜索