java的動態代理學習
此前總是在hadoop的學習中聽說這個名詞,但是總是不知道其所以然。今天有空來好好的研究研究。
所有的編程代碼學習都離不開示例,尤其是自己編寫代碼。下面是一個示例:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
*
* 理解理解java的代理機制(proxy) <br>
* 1.代理機制的現實版演繹: <br>
* 就拿買火車票來說,我、黃牛、火車代售站;這裏有三個人,我是需要買票的客戶,我可以直接在“火車代售站”買票,也可以找“黃牛”買票,也就是說
* “黃牛”可以代理火車代售站。即我和黃牛交易與我和火車代售站交易一樣,黃牛代理了火車代售站:都有一個賣票的窗口 <br>
* 2.java的代理機制 <br>
* B類持有A類的引用,調用A類就相當於當用B類(好繞口)
*
* @author fangshun.liao
* @version 2015年1月12日 下午2:41:52
*/
public class ProxyTest {
public static void main(String args[]) {
Real real = new Real();
RealInterface proxy = (RealInterface) Proxy.newProxyInstance(real.getClass()
.getClassLoader(), new Class[]{RealInterface.class}, new InvocationReal(real));
proxy.print();
}
}
interface RealInterface{
public void print();
}
class Real implements RealInterface{
public void print() {
System.out.println("do Real");
}
}
class InvocationReal implements InvocationHandler {
private Real real;
public InvocationReal( Real real) {
this.real = real;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("proxy invocation starting...");
method.invoke(real, args);
System.out.println(method.getName());
System.out.println("proxy invocation ended");
return proxy;
}
}
上面的代碼是一個非常簡單的示例,就是將本來new好的對象再用Proxy包裝起來:
RealInterface proxy = (RealInterface) Proxy.newProxyInstance(real.getClass() .getClassLoader(),
new Class[]{RealInterface.class}, new InvocationReal(real));
這個方法看起來好怪啊,classLoader都出來了,且第二個必須是interface才行,第三個也不能爲空,且又引用了real。當看到這時會發現,這樣子寫程序真是腦子有毛病呢!明明就有個real了,乾脆直接調用real就好了,還要繞來繞去幹什麼?是的,如果就光這個例子,是不能夠說明什麼問題的,且反而感覺憋手蹩腳的。不妨先不考慮動態代理到底有什麼用,而是猜猜它的實現方式即執行方式:
1.參數說明及用途猜想
classLoader:其可以解析出java字節碼(.class)文件,那麼如果我們能夠生成java的字節碼,我們就可以用此加載出一個類;更進一步:在知道所有方法名時就可以自動生成一個類。這裏猜測newProxyInstance方法會自己創造一個類,這個類實現了interfaces。這樣解釋有點說的通此方法返回值必須是interfaces中的一個,而不能是類(如
Real類)。
InvocationHandler:方法調用的句柄,也就是proxy調用方法後最終真正實現的地方。且看其實現方式:
class InvocationReal implements InvocationHandler {
private Real real;//其實這個InvocationReal 和Real可以沒有任何關係
public InvocationReal( Real real) {
this.real = real;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)//proxy爲方法所依賴的代理,method即調用的方法實體類,args就是參數列表
throws Throwable {
System.out.println("proxy invocation starting...");
method.invoke(real, args);//這裏真正執行方法
System.out.println(method.getName());
System.out.println("proxy invocation ended");
return proxy;
}
}
2.proxy.print()的調用過程猜想
上面獲得了一個proxy,下面就可以來調用方法了:proxy.print();
要想知道此方法最終是怎麼以一個進行過程,可以debug跟蹤。會發現其是直接跳入到了InvocationReal .invoke(...)方法中的。這裏如果再和上面的猜測結合,這裏我們可以自己動手寫一個將就的動態代理的實現了,下面將我自己要實現的方法的僞代碼給出:
Proxy.newProxyInstance(real.getClass() .getClassLoader(),
new Class[]{RealInterface.class}, new InvocationReal(real)):
1.Object [] its = getAllInterfaces();//獲取所有的接口
2. Object cl = generateClassFromInterfaces(its,InvocationHandler);//根據interfaces生成一個類,這個類實現了所有的接口
3. generateClassFromInterfaces(its):
3. generateClassFromInterfaces(its):
==>每一個方法的調用就是:
try {
Method method = this.getClass().getMethod("print", new Class[]{});
Object args[] = new Object[1];
args[0] = null;//因爲此處沒有輸入參數
InvocationReal.invoke(this,method,args);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
實驗後發現proxy的名字就是com.xxx.xxx.xxx_test.$Proxy0,看來猜測是猜對了。
原理猜測出來,那麼什麼地方用到了呢?這個代理其實在遠程過程調用(RPC)見的最多,不妨思考思考,在newProxyInstance時我們只要接口就行了,具體怎麼實現的都可以不用管,然後在invocationHandler的invoke中我們可以做非常多的事情,如將method序列化發送出去,然後等待遠處返回結果等等。即我們自己寫一個invocationHandler就行,至於invoke中怎麼寫就隨便你了。這樣看來是不是感覺非常牛叉?
其實代理模式就是A持有B的引用,調用A的方法全部由B真正執行。