生成代理類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次數。
代理