代理模式
相信了解設計模式的developer對代理(proxy pattern)模式都不陌生伐憾。代理模式的基本思想就是在調(diào)用者和被調(diào)用者之間加上一層“代理”壮韭,這層代理對于調(diào)用者而言是透明的,因為代理往往和被代理對象實現(xiàn)相同的借口枉圃。那么既然實現(xiàn)相同的接口遮糖,代理的意義又何在落追?因為我們常常需要在原本的接口上封裝一些業(yè)務邏輯,比如日志偿洁、緩存撒汉、訪問控制等等,這些另外封裝的業(yè)務邏輯從設計的角度并不適宜直接封裝在原有的接口實現(xiàn)中父能,因為諸如日志神凑、緩存等并不屬于被代理對象的職責;同時何吝,代理模式可以做到方便的修改和移除(設計模式的關鍵就是封裝變化)溉委。這種模式在RMI和EJB中都得到了很好的體現(xiàn),包括spring的AOP中實現(xiàn)爱榕。傳統(tǒng)的代理模式需要在源代碼中添加一些附加的類瓣喊,一般需要手工編寫或工具自動生成。
動態(tài)代理
java的動態(tài)代理比代理的思想更進了一步黔酥,因為它可以動態(tài)的創(chuàng)建代理并動態(tài)的處理對所代理方法的調(diào)用藻三,在運行時刻,可以動態(tài)創(chuàng)建出一個實現(xiàn)了多個接口的代理類跪者。每個代理類的對象都會關聯(lián)一個表示內(nèi)部處理邏輯的InvocationHandler 接 口的實現(xiàn)棵帽。當使用者調(diào)用了代理對象所代理的接口中的方法的時候,這個調(diào)用的信息會被傳遞給InvocationHandler的invoke方法渣玲。在 invoke方法的參數(shù)中可以獲取到代理對象逗概、方法對應的Method對象和調(diào)用的實際參數(shù)。invoke方法的返回值被返回給使用者忘衍。這種做法實際上相 當于對方法調(diào)用進行了攔截逾苫。下面以一個簡單的示例來說明java動態(tài)代理:
接口定義:
interface DoSomething {
void doSomething();
void doSomethingElse(String arg);
}
實現(xiàn)接口的類,也即被代理的類:
class RealObject implements DoSomething {
@Override
public void doSomething() {
System.out.println("realObject doSomething");
}
@Override
public void doSomethingElse(String arg) {
System.out.println("realObject doSomethingElse " + arg);
}
}
動態(tài)代理的處理類枚钓,需要實現(xiàn)InvocationHandler
接口:
class DynamicProxyHander implements InvocationHandler {
// 被代理類,將其作為實例變量在構(gòu)造函數(shù)中傳入
private Object proxied;
public DynamicProxyHander(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("***** proxy: " + proxy.getClass().getSimpleName() + "method :" + method + "args: " + args);
if (null != args) {
for (Object arg : args) {
System.out.println(" " + arg);
}
}
return method.invoke(proxied, args);
}
}
調(diào)用RealObject
和調(diào)用動態(tài)代理生成對象:
public class DynamicProxyDemo{
public static void consume(DoSomething doSomething){
doSomething.doSomething();
doSomething.doSomethingElse("bonbo");
}
public static void main(String[] args) {
RealObject real = new RealObject();
consume(real);
DoSomething proxy = (DoSomething)Proxy.newProxyInstance(DoSomething.class.getClassLoader(),
new Class[]{DoSomething.class}, new DynamicProxyHander(real));
consume(proxy);
}
}
通過調(diào)用Proxy.newProxyInstance
可以創(chuàng)建動態(tài)代理铅搓,這個方法需要一個類加載器,一個希望該代理實現(xiàn)的接口列表搀捷,以及InvocationHandler
的一個實現(xiàn)星掰。動態(tài)代理可以將所有的調(diào)用重定向到調(diào)用處理器,因此通常會向調(diào)用處理器的構(gòu)造器傳遞一個實際對象的引用,從而使動態(tài)代理處理中介任務時蹋偏,可以將請求轉(zhuǎn)發(fā)便斥。
動態(tài)代理與字節(jié)碼生成技術
上面說的是動態(tài)代理的基本用法,相信許多java開發(fā)者都使用過動態(tài)代理威始,即使沒有直接 使用過java.lang.reflect.Proxy
或?qū)崿F(xiàn)過InvocationHandler
接口枢纠,也應該使用過spring做過Bean
的管理。如果使用過Spring黎棠,那大多數(shù)情況下都會用過動態(tài)代理晋渺,因為如果bean是面向接口編程,那么在spring內(nèi)部都是用動態(tài)代理對bean進行增強的脓斩。動態(tài)代理中所謂的動態(tài)木西,是針對代碼實際編寫了代理的“靜態(tài)”而言的,動態(tài)代理的優(yōu)勢不在于減少了那一點的編碼量随静,而是實現(xiàn)了在原始類和接口未知的時候八千,就確定代理的代理行為,當代理類和原始類脫離直接聯(lián)系后燎猛,就可以和靈活的重用到不同的應用場景中恋捆。
在上述的代碼里,唯一的黑匣子就是Proxy.newProxyInstance
方法重绷,除此之外并無任何特別之處沸停。這個方法返回了一個實現(xiàn)了DoSomething
的接口。跟蹤這個方法的源碼:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
我們可以看到程序進行了驗證昭卓、優(yōu)化愤钾、緩存、同步候醒、生成字節(jié)碼能颁、顯式類加載等操作,最后調(diào)用了sun.misc.ProxyGenerator.generateProxyClass
方法來完成生成字節(jié)碼的動作(上述源代碼只貼出了主方法倒淫,詳細步驟有興趣的讀者可以參閱java.lang.reflect.Proxy
源代碼)伙菊。這個方法可以在運行時產(chǎn)生一個描述代理類的字節(jié)碼byte[]
數(shù)組。大致的生成過程其實就是根據(jù).class
文件的格式規(guī)范去拼裝字節(jié)碼昌简,但在實際開發(fā)中,直接生成字節(jié)碼的例子極為少見绒怨,如果有大量操作字節(jié)碼的需求纯赎,還是使用封裝好的字節(jié)碼類庫比較合適。(關于字節(jié)碼格式以及類加載過程南蹂,讀者可以自行查閱資料學習)
動態(tài)代理的運用
動態(tài)代理由于本身靈活的特性犬金,在Java技術棧中得到了非常多的運用。比如為java開發(fā)者所熟悉的spring框架,其AOP本質(zhì)上也是動態(tài)代理晚顷。包括hadoop RPC在內(nèi)的許多RPC框架也大量運用了動態(tài)代理峰伙,在日后的學習和實踐中,會多多關注所運用的工具和框架的實現(xiàn)機制该默,若有所感悟和收獲瞳氓,會記錄一下以供總結(jié)和分享。