动态代理
日常开发中,我们经常会遇到一些与我们业务无关却又无法避免的需求,比如:统计每个接口的访问量、给每个接口打印日志……等等,这些都是很常见的需求。如果在每个接口里编写增加访问量或者打印日志的代码,势必会引入一些冗余且无关业务的代码。
因此,Java提出动态代理的概念,将我们的主业务放在被代理类中执行,而与业务关系并非不大但的代码则放在调用句柄InvocationHandler中执行,调用句柄会通过反射的方式,调用被代理类的方法。通过调用句柄创建代理类,来实现动态代理。为了实现动态代理,我们需要了解:java.lang.reflect.InvocationHandler调用句柄接口和java.lang.reflect.Proxy类。
接口:
package com.leolin.jvm.bytecode; public interface Subject { void request(); }
被代理类,即执行主业务的类:
package com.leolin.jvm.bytecode; public class RealSubject implements Subject { @Override public void request() { System.out.println("From RealSubject");//主业务 } }
调用句柄:
package com.leolin.jvm.bytecode; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class SubjectHandler implements InvocationHandler { private Object object; public SubjectHandler(Object object) { this.object = object; //被代理对象,这里传入RealSubject实例 } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //代理对象在执行方法时,会执行调用句柄的invoke方法,我们在被代理对象执行方法的前后,添加打印操作 System.out.println("Before Call method:" + method); //通过反射调用被代理对象的方法 Object result = method.invoke(this.object, args); System.out.println("After Call method:" + method); return result; } }
调用句柄一般执行诸如:打印日志、统计接口访问量、打开数据库连接和释放数据库连接等等。句柄中我们声明了一个成员变量object,在创建句柄时,我们会把之前的RealSubject实例传进去,当执行代理对象的调用方法时,会转而执行句柄的invoke方法,而method.invoke(Object obj, Object... args)就是对被代理对象进行方法调用,我们可以在这一句的前后添加我们要执行的业务逻辑。
客户端:
package com.leolin.jvm.bytecode; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class Client { public static void main(String[] args) { //主业务对象(被代理对象) RealSubject rs = new RealSubject(); //通过被代理对象构造出调用句柄 InvocationHandler ds = new SubjectHandler(rs); Class<?> cls = rs.getClass(); /* * 通过Proxy类生成被代理对象,这里需要传入三个参数: * 第一个参数:被代理对象的类加载器 * 第二个参数:被代理对象所实现的接口 * 第三个参数:封装被代理对象的句柄 * */ Subject subject = (Subject) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), ds); subject.request(); } }
运行代码,得到如下结果:
Before Call method:public abstract void com.leolin.jvm.bytecode.Subject.request() From RealSubject After Call method:public abstract void com.leolin.jvm.bytecode.Subject.request()
可以看到,我们通过Proxy类所生成的代理对象,在执行request()的时候,会在调用被代理对象RealSubject.request()的前后,执行我们所编写的打印代码。
这里,我们看看在Proxy.newProxyInstance()方法中,都发生了什么:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); //com.sun.proxy.$Proxy0程序运行期动态创建出来 System.out.println(subject.getClass()); System.out.println(subject.getClass().getSuperclass()); 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<Void>() { 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); } }
我们重点关注第22、32、42行上。22行,我们通过getProxyClass0获取到代理类的class对象,32行我们通过class对象获取到一个构造方法,这个构造方法要求传入一个调用句柄参数,而在42行,我们将之前传入的调用句柄作为参数,传给构造方法,生成代理对象。32行和42行都好理解,于是我们又将重点转移到22行,getProxyClass0方法是如何生成代理对象的class对象呢?我们来看下getProxyClass0的方法:
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // If the proxy class defined by the given loader implementing // the given interfaces exists, this will simply return the cached copy; // otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces); }
getProxyClass0所生成的代理类,要求实现的接口数不超过65535,一般业务开发不会达到这个量级,接下来就是从proxyClassCache中获取一个class对象,我们重点看上面代码的注释:如果代理类的class对象已经存在于类加载器的实现,则返回一份缓存中的拷贝,否则代理类的class对象将由ProxyClassFactory创建。
最开始,代理类的class对象一定不存在于proxyClassCache中,所以我们来看看ProxyClassFactory是如何生成代理类的class对象:
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> { // prefix for all proxy class names private static final String proxyClassNamePrefix = "$Proxy"; // next number to use for generation of unique proxy class names private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class<?> intf : interfaces) { /* * Verify that the class loader resolves the name of this * interface to the same Class object. */ Class<?> interfaceClass = null; try { interfaceClass = Class.forName(intf.getName(), false, loader); } catch (ClassNotFoundException e) { } if (interfaceClass != intf) { throw new IllegalArgumentException( intf + " is not visible from class loader"); } /* * Verify that the Class object actually represents an * interface. */ if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } /* * Verify that this interface is not a duplicate. */ if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } String proxyPkg = null; // package to define proxy class in int accessFlags = Modifier.PUBLIC | Modifier.FINAL; /* * Record the package of a non-public proxy interface so that the * proxy class will be defined in the same package. Verify that * all non-public proxy interfaces are in the same package. */ for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String name = intf.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 + "."; } /* * Choose a name for the proxy class to generate. */ long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * Generate the specified proxy class. */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { /* * A ClassFormatError here means that (barring bugs in the * proxy class generation code) there was some other * invalid aspect of the arguments supplied to the proxy * class creation (such as virtual machine limitations * exceeded). */ throw new IllegalArgumentException(e.toString()); } } }
如果从proxyClassCache中获取class对象,却发现class对象不存在,最终程序会执行到ProxyClassFactory.apply(ClassLoader loader, Class<?>[] interfaces)方法来创建一个class对象。ProxyClassFactory.apply方法中,会校验所传入的类加载器是否有权限加载类,构造代理对象的类名和访问标志,最终通过ProxyGenerator.generateProxyClass(final String var0, Class<?>[] var1, int var2)这个方法,生成一个字节数组,这个字节数组就是我们的class对象。于是,我们转而看一下ProxyGenerator.generateProxyClass这个方法:
public class ProxyGenerator { …… private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles")); …… public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) { ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2); final byte[] var4 = var3.generateClassFile(); if (saveGeneratedFiles) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { try { int var1 = var0.lastIndexOf(46); Path var2; if (var1 > 0) { Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar)); Files.createDirectories(var3); var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class"); } else { var2 = Paths.get(var0 + ".class"); } Files.write(var2, var4, new OpenOption[0]); return null; } catch (IOException var4x) { throw new InternalError("I/O exception saving generated file: " + var4x); } } }); } return var4; } …… }
这段代码中会生成一个类型为ProxyGenerator的变量var3,这个变量在调用generateClassFile()方法时,会根据我们传入的类名、实现接口和访问标志生成一个字节数组,要知道,一个class文件本身就是一个字节数组,在这个方法中,还会为我们重写代理类的的hashCode()、equals(Object obj)和toString()方法,因为篇幅的原因,这里就不再多介绍generateClassFile()方法。这里我们注意到上面有个环境变量:sun.misc.ProxyGenerator.saveGeneratedFiles,一般在运行期生成的代理类的class对象,在我们工程下面是不会有class文件的,但如果我们修改这个变量为true,会将运行期生成代理类的class文件。这里我们在Client.main(String[] args)函数的开头添加如下代码:
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
重新执行Client的代码,可以看到在我们工程下面多出一个目录:com.sun.proxy,这个目录下面有个$Proxy0.class文件,就是我们的代理类的class文件,我们用idea反编译的结果看看对应的Java代码:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.sun.proxy; import com.leolin.jvm.bytecode.Subject; 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 Subject { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final void request() throws { try { super.h.invoke(this, m3, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m3 = Class.forName("com.leolin.jvm.bytecode.Subject").getMethod("request"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
可以看到,代理类$Proxy0的构造参数要求传入一个InvocationHandler调用句柄的实例,然后再将InvocationHandler实例传给父类Proxy的构造方法。代理类中还有4个Method类型的静态变量,Method类型可以帮助我们在程序运行期间,执行某个对象的目标方法。在静态代码块中分别给这4个Method变量赋值,m1、m2、m3、m4分别用于执行equals、toString、request、hashCode方法,而代理类重写之前的四个方法,并将对应的Method变量传入到调用句柄的invoke方法,调用句柄最终会执行被代理对应对应的方法,如果被代理对象本身没有重写该方法,如:equals、toString和hashCode,则调用父类的方法。