動態代理原理

生成代理類java

//獲取代理類
Class cl = getProxyClass(loader, interfaces);
//獲取帶有InvocationHandler參數的構造方法
Constructor cons = cl.getConstructor(constructorParams);
//把handler傳入構造方法生成實例
return (Object) cons.newInstance(new Object[] { h });  
其中getProxyClass(loader, interfaces)方法用於獲取代理類,它主要作了三件事情:在當前類加載器的緩存裏搜索是否有代理類,沒有則生成代理類並緩存在本地JVM裏。清單三:查找代理類。緩存

 // 緩存的key使用接口名稱生成的List
Object key = Arrays.asList(interfaceNames);
synchronized (cache) {
    do {
Object value = cache.get(key);
         // 緩存裏保存了代理類的引用
if (value instanceof Reference) {
    proxyClass = (Class) ((Reference) value).get();
}
if (proxyClass != null) {
// 代理類已經存在則返回
    return proxyClass;
} else if (value == pendingGenerationMarker) {
    // 若是代理類正在產生,則等待
    try {
cache.wait();
    } catch (InterruptedException e) {
    }
    continue;
} else {
    //沒有代理類,則標記代理準備生成
    cache.put(key, pendingGenerationMarker);
    break;
}
    } while (true);
}

代理類的生成主要是如下這兩行代碼。 清單四:生成並加載代理類性能

//生成代理類的字節碼文件並保存到硬盤中(默認不保存到硬盤)
proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
//使用類加載器將字節碼加載到內存中
proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);

ProxyGenerator.generateProxyClass()方法屬於sun.misc包下,Oracle並無提供源代碼,可是咱們可使用JD-GUI這樣的反編譯軟件打開jre\lib\rt.jar來一探究竟,如下是其核心代碼的分析。
清單五:代理類的生成過程測試


//添加接口中定義的方法,此時方法體爲空
for (int i = 0; i < this.interfaces.length; i++) {
  localObject1 = this.interfaces[i].getMethods();
  for (int k = 0; k < localObject1.length; k++) {
     addProxyMethod(localObject1[k], this.interfaces[i]);
  }
}

//添加一個帶有InvocationHandler的構造方法
MethodInfo localMethodInfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);

//循環生成方法體代碼(省略)
//方法體裏生成調用InvocationHandler的invoke方法代碼。(此處有所省略)
this.cp.getInterfaceMethodRef("InvocationHandler", "invoke", "Object; Method; Object;")

//將生成的字節碼,寫入硬盤,前面有個if判斷,默認狀況下不保存到硬盤。
localFileOutputStream = new FileOutputStream(ProxyGenerator.access$000(this.val$name) + ".class");
localFileOutputStream.write(this.val$classFile); this


  那麼經過以上分析,咱們能夠推出動態代理爲咱們生成了一個這樣的代理類。把方法doSomeThing的方法體修改成調用LogInvocationHandler的invoke方法。
清單六:生成的代理類源碼spa

package sample.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class ProxyBusiness{

    private LogInvocationHandler h;

    public void doBusiness2() {
        try {
            Method m = (h.target).getClass().getMethod("doBusiness2", null);
            h.invoke(this, m, null);
        } catch (Throwable e) {
            // 異常處理(略)
        }
    }

    public boolean doBusiness1() {
        try {
            Method m = (h.target).getClass().getMethod("doBusiness1", null);
            return (Boolean) h.invoke(this, m, null);
        } catch (Throwable e) {
            // 異常處理(略)
        }
        return false;
    }

    public ProxyBusiness(LogInvocationHandler h) {
        this.h = h;
    }
    
    /**
     * 打印日誌的切面
     */
    public static class LogInvocationHandler implements InvocationHandler {

        private Object target; // 目標對象

        LogInvocationHandler(Object target) {
            this.target = target;
        }

        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            // 執行原有邏輯
            Object rev = method.invoke(target, args);
            // 執行織入的日誌,你能夠控制哪些方法執行切入邏輯
            if (method.getName().equals("doBusiness2")) {
                System.out.println("記錄日誌");
            }
            return rev;
        }
    }

    // 測試用
    public static void main(String[] args) {
        // 構建AOP的Advice
        LogInvocationHandler handler = new LogInvocationHandler(new Business());
        new ProxyBusiness(handler).doBusiness1();
        new ProxyBusiness(handler).doBusiness2();
    }
}

小結 
    從前兩節的分析咱們能夠看出,動態代理在運行期經過接口動態生成代理類,這爲其帶來了必定的靈活性,但這個靈活性卻帶來了兩個問題,第一代理類必須實現一 個接口,若是沒實現接口會拋出一個異常。第二性能影響,由於動態代理使用反射的機制實現的,首先反射確定比直接調用要慢,通過測試大概每一個代理類比靜態代 理多出10幾毫秒的消耗。其次使用反射大量生成類文件可能引發Full GC形成性能影響,由於字節碼文件加載後會存放在JVM運行時區的方法區(或者叫持久代)中,當方法區滿的時候,會引發Full GC,因此當你大量使用動態代理時,能夠將持久代設置大一些,減小Full GC次數。
代理

相關文章
相關標籤/搜索