衆所周知Java的動態代理由Proxy和InvocationHander實現。以下代碼演示了簡單地應用:
public interface IFoo { public void load(int i); public void save(Object o); public List<Object> list(); }
public class Foo implements IFoo { public void load(int i) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("load() is running"); } public void save(Object o) { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("save() is running"); } public List<Object> list() { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("list() is running"); return null; } }
用動態代理實現計算運行時間和打印日誌。
public class MyProxy { public static void main(String[] args) { time(); log(); } static void time() { final Foo foo = new Foo(); IFoo myFoo = (IFoo) Proxy.newProxyInstance( MyProxy.class.getClassLoader(), foo.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = new Date().getTime(); Object rtn = method.invoke(foo, args); long end = new Date().getTime(); System.out.println(method.getName() + " method takes " + TimeUnit.MILLISECONDS.toSeconds(end - start) + " seconds"); return rtn; } }); System.out.println(myFoo.getClass()); myFoo.list(); myFoo.load(5); myFoo.save(""); } static void log() { final Foo foo = new Foo(); IFoo myFoo = (IFoo) Proxy.newProxyInstance( MyProxy.class.getClassLoader(), foo.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object rtn = method.invoke(foo, args); System.out.println(method.getName() + "(" + (args == null ? "" : args) + ") runs at " + new Date()); return rtn; } }); System.out.println(myFoo.getClass()); myFoo.list(); myFoo.load(5); myFoo.save(""); } }
運行結果如下:
class com.sun.proxy.$Proxy0
list() is running
list method takes 3 seconds
load() is running
load method takes 1 seconds
save() is running
save method takes 2 seconds
class com.sun.proxy.$Proxy0
list() is running
list() runs at Fri Mar 28 17:52:13 CST 2014
load() is running
load([Ljava.lang.Object;@961dff) runs at Fri Mar 28 17:52:14 CST 2014
save() is running
save([Ljava.lang.Object;@18b81e3) runs at Fri Mar 28 17:52:16 CST 2014
可以看到動態生成了類型$Proxy0的對象即完成計時和日誌功能的代理對象。而這裏最核心的部分毫無疑問是newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h),這個方法返回一個實例,該實例繼承了Proxy類並實現了IFoo接口,並接受一個InvocationHandler作爲成員變量,而每一個方法的具體實現實際上調用的都是實現InvocationHandler接口的匿名內部類對象的invoke(Object proxy, Method method,Object[] args) 方法,在該方法內部通過反射延續了被代理對象的行爲。
接下來,我們通過查看一些源碼片段來做更深入地瞭解:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { if (h == null) { throw new NullPointerException(); } final SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkProxyAccess(Reflection.getCallerClass(), loader, interfaces); } /* * Look up or generate the designated proxy class. */ Class<?> cl = getProxyClass0(loader, interfaces); /* * Invoke its constructor with the designated invocation handler. */ try { final Constructor<?> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) { // create proxy instance with doPrivilege as the proxy class may // implement non-public interfaces that requires a special permission return AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { return newInstance(cons, ih); } }); } else { return newInstance(cons, ih); } } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } }
這裏的核心是Class<?> cl = getProxyClass0(loader, interfaces),而後面的部分是通過反射實例化一個代理對象。
選取部分代碼來解讀生成代理類Class的大概步驟,首先是定義class name。它由三部分組成proxyPkg + proxyClassNamePrefix + num。package name,如果沒有非公共接口即採用com.sun.proxy作爲包名加上$Proxy和自增長的數字,這也就是上文運行結果中看到的com.sun.proxy.$Proxy0。而接下來纔是更加核心的部分生成符合JVM規範的Class字節碼。
try { String proxyPkg = null; // package to define proxy class in for (int i = 0; i < interfaces.length; i++) { int flags = interfaces[i].getModifiers(); if (!Modifier.isPublic(flags)) { String name = interfaces[i].getName(); int n = name.lastIndexOf('.'); String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { // if no non-public proxy interfaces, use com.sun.proxy package proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } { long num; synchronized (nextUniqueNumberLock) { num = nextUniqueNumber++; } String proxyName = proxyPkg + proxyClassNamePrefix + num; byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); try { proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } } proxyClasses.put(proxyClass, null); }
通過以下代碼片段可以看出它按照JVM規範輸出字節碼,魔數、常量池、訪問標誌、類索引等等
// u4 magic; dout.writeInt(0xCAFEBABE); // u2 minor_version; dout.writeShort(CLASSFILE_MINOR_VERSION); // u2 major_version; dout.writeShort(CLASSFILE_MAJOR_VERSION); cp.write(dout); // (write constant pool) // u2 access_flags; dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER); // u2 this_class; dout.writeShort(cp.getClass(dotToSlash(className))); // u2 super_class; dout.writeShort(cp.getClass(superclassName)); // u2 interfaces_count;
而下面這段代碼也部分印證了代理對象的方法實際上是對InvocationHandler的invoke方法的調用。
out.writeShort(cp.getInterfaceMethodRef( "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;" + "[Ljava/lang/Object;)Ljava/lang/Object;"));
但從這些字節碼我們似乎還很難一睹這個代理類的全貌,爲了一睹爲快,我嘗試藉助Instrumentaion來獲取內存中的這個Class對象(當然我們可以通過修改JDK源碼)。
public class MyTransformer implements ClassFileTransformer { public static void premain(String options, Instrumentation ins) { ins.addTransformer(new MyTransformer()); } public byte[] transform(ClassLoader loader, String className, Class cBR, java.security.ProtectionDomain pD, byte[] classfileBuffer) throws IllegalClassFormatException { if (className.contains("com/sun/proxy/$Proxy0")) { try { FileChannel fc = new FileOutputStream("D:\\$Proxy0.class") .getChannel(); fc.write(ByteBuffer.wrap(classfileBuffer)); fc.close(); } catch (IOException e) { e.printStackTrace(); } } return classfileBuffer; } }
然後打包成Jar,設置Premain-Class,指定javaagent運行,得到class文件,然後反編譯,end of story
package com.sun.proxy; import IFoo; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import java.util.List; public final class $Proxy0 extends Proxy implements IFoo { private static Method m1; private static Method m5; private static Method m0; private static Method m3; private static Method m4; private static Method m2; public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } public final boolean equals(Object paramObject) throws { try { return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void save(Object paramObject) throws { try { this.h.invoke(this, m5, new Object[] { paramObject }); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() throws { try { return ((Integer)this.h.invoke(this, m0, null)).intValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void load(int paramInt) throws { try { this.h.invoke(this, m3, new Object[] { Integer.valueOf(paramInt) }); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final List list() throws { try { return (List)this.h.invoke(this, m4, null); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() throws { try { return (String)this.h.invoke(this, m2, null); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m5 = Class.forName("IFoo").getMethod("save", new Class[] { Class.forName("java.lang.Object") }); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); m3 = Class.forName("IFoo").getMethod("load", new Class[] { Integer.TYPE }); m4 = Class.forName("IFoo").getMethod("list", new Class[0]); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } }