之前使用cglib的時候不需要將classLoader作為參數傳入,但動態代理卻要,帶着這個疑惑進入這個方法:
Proxy.newProxyInstance(classLoader, interfaces, InvocationHandler)
要在classLoader里去找interfaces,如果也加載進來了才能繼續執行,並且用ProxyGenerator動態生成了一個代理類的字節碼文件(使用了緩存技術,只需要生成一次),然后用classLoader將這個字節碼文件加載進來。這就是classLoader的作用。
可以這樣看生成的字節碼類。
加入執行參數:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true")
生成的字節碼文件就會保留下來,然后編譯出來如下:
package demo; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy0 extends Proxy implements IA { private static Method m1; private static Method m4; private static Method m3; private static Method m0; private static Method m2; public $Proxy0(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler); } public final boolean equals(Object paramObject) { try { return ((Boolean) this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int b(String paramString) { try { return ((Integer) this.h.invoke(this, m4, new Object[] { paramString })).intValue(); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void a() { try { this.h.invoke(this, m3, null); return; } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() { try { return ((Integer) this.h.invoke(this, m0, null)).intValue(); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() { try { return (String) this.h.invoke(this, m2, null); } catch (RuntimeException localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m4 = Class.forName("demo.IA").getMethod("b", new Class[] { Class.forName("java.lang.String") }); m3 = Class.forName("demo.IA").getMethod("a", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } }
可以發現所有接口方法的實現都委托給InvocationHandler的invoke方法了,這也就是實現代理模式的地方了。
--------------------------------------------------------------------------
cglib不需要傳入ClassLoader,代碼里會自己去找上下文的ClassLoader,這種設計使少傳一個ClassLoader這種很少見的參數對初學者來說用起來要簡單點。
可以設置System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "字節碼文件保存位置",把cglib生成的動態字節碼保存下來。
單間分析下生成的字節碼
動態生成的繼承類會改寫我們使用的父類的所有方法,攔截下來交給設置的MethodInterceptor去執行。
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy)
第一個參數obj就是動態生成的子類。第二個參數是原始類的方法。
我們一般使用proxy.invokeSuper(obj,args)方法。這個很好理解,就是執行原始類的方法。還有一個方法proxy.invoke(obj,args),這是執行生成子類的方法。如果傳入的obj就是子類的話,會發生內存溢出,因為子類的方法不挺地進入intercept方法,而這個方法又去調用子類的方法,兩個方法直接循環調用了。
我們來看看MethodProxy,原始類里每一個方法都會在動態的子類里有一個對應的MethodProxy,而一個MethodProxy又對應了兩個動態生成的FastClass類,一個是對應原始方法,一個對應新生成的子類,MethodProxy.invokeSuper就是交給對應原始方法那個FastClass,MethodProxy.invoke交給另一個。
這2個額外生成的類作用在於當我們調用一個方法時,不通過反射來調用,而是通過類似於數組下標的方式來定位方法,直接進行類方法的執行。
FastClass生成的代碼類似這樣的
public Object invoke(int paramInt, Object paramObject, Object[] paramArrayOfObject) throws InvocationTargetException { // Byte code: 0: aload_2 1: checkcast 159 net/sf/cglib/mytest/A$$EnhancerByCGLIB$$f84d7df 4: iload_1 //paramInt參數入棧 5: tableswitch default:+403 -> 408, 0:+131->136..... //通過paramInt也就相當於數組小標志,定位到方法執行的代碼段 ..... ..... 148: aload_3 149: iconst_0 150: aaload 151: invokevirtual 166 net/sf/cglib/mytest/A$$EnhancerByCGLIB$$f84d7df:equals (Ljava/lang/Object;)Z //直接快速的執行方法 ..... ..... } }