java的動態代理學習隨記

動態代理有什麼作用及應用場景

  1. 日誌集中打印

  2. 事物管理

  3. 權限管理

  4. AOP

在Spring AOP中可以有哪些實現方式和區別

  1. java Proxy(動態構建字節碼)

  2. cflib(動態構建字節碼)

  3. Asnecti(修改目標類的字節、織入代理的字節)

  4. instrumentation(修改目標類的字節碼、類裝載的時候動態攔截去修改基於javaagent)

動態代理的底層有什麼密碼

解釋:無論哪種方式實現動態代理其本質實現都是對字節碼的修改,其區別是從哪裡進行切入去修改字節碼

動態代理技術棧

java的動態代理學習隨記

動態代理

java的動態代理比代理的思想更進了一步,因為它可以動態的創建代理並動態的處理對所代理方法的調用,在運行時刻,可以動態創建出一個實現了多個接口的代理類。每個代理類的對象都會關聯一個表示內部處理邏輯的InvocationHandler 接 口的實現。當使用者調用了代理對象所代理的接口中的方法的時候,這個調用的信息會被傳遞給InvocationHandler的invoke方法。在 invoke方法的參數中可以獲取到代理對象、方法對應的Method對象和調用的實際參數。invoke方法的返回值被返回給使用者。這種做法實際上相 當於對方法調用進行了攔截。下面以一個簡單的示例來說明java動態代理:

接口定義:

interface DoSomething {

void doSomething();

void doSomethingElse(String arg);

}

實現接口的類,也即被代理的類:

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);

}

}

動態代理的處理類,需要實現InvocationHandler接口:

class DynamicProxyHander implements InvocationHandler {

// 被代理類,將其作為實例變量在構造函數中傳入

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);

}

}

調用RealObject和調用動態代理生成對象:

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);

}

}

通過調用Proxy.newProxyInstance可以創建動態代理,這個方法需要一個類加載器,一個希望該代理實現的接口列表,以及InvocationHandler的一個實現。動態代理可以將所有的調用重定向到調用處理器,因此通常會向調用處理器的構造器傳遞一個實際對象的引用,從而使動態代理處理中介任務時,可以將請求轉發。

動態代理與字節碼生成技術

上面說的是動態代理的基本用法,相信許多java開發者都使用過動態代理,即使沒有直接 使用過java.lang.reflect.Proxy或實現過InvocationHandler接口,也應該使用過spring做過Bean的管理。如果使用過Spring,那大多數情況下都會用過動態代理,因為如果bean是面向接口編程,那麼在spring內部都是用動態代理對bean進行增強的。動態代理中所謂的動態,是針對代碼實際編寫了代理的“靜態”而言的,動態代理的優勢不在於減少了那一點的編碼量,而是實現了在原始類和接口未知的時候,就確定代理的代理行為,當代理類和原始類脫離直接聯繫後,就可以和靈活的重用到不同的應用場景中。

在上述的代碼裡,唯一的黑匣子就是Proxy.newProxyInstance方法,除此之外並無任何特別之處。這個方法返回了一個實現了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() {

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);

}

}

我們可以看到程序進行了驗證、優化、緩存、同步、生成字節碼、顯式類加載等操作,最後調用了sun.misc.ProxyGenerator.generateProxyClass方法來完成生成字節碼的動作(上述源代碼只貼出了主方法,詳細步驟有興趣的讀者可以參閱java.lang.reflect.Proxy源代碼)。這個方法可以在運行時產生一個描述代理類的字節碼byte[]數組。大致的生成過程其實就是根據.class文件的格式規範去拼裝字節碼,但在實際開發中,直接生成字節碼的例子極為少見,如果有大量操作字節碼的需求,還是使用封裝好的字節碼類庫比較合適。(關於字節碼格式以及類加載過程,讀者可以自行查閱資料學習)

動態代理的運用

動態代理由於本身靈活的特性,在Java技術棧中得到了非常多的運用。比如為java開發者所熟悉的spring框架,其AOP本質上也是動態代理。包括hadoop RPC在內的許多RPC框架也大量運用了動態代理,在日後的學習和實踐中,會多多關注所運用的工具和框架的實現機制,若有所感悟和收穫,會記錄一下以供總結和分享。


分享到:


相關文章: