虽然面向应用开发的程序员很少直接使用动态代理技术,但是诸如AOP,事务控制,Spring容器注入等等,实际上都是基于动态代理实现的,可见,动态代理是多么重要。这篇随笔记录了我对动态代理技术原理的一两点理解。
1. 什么是代理
1.1 什么是代理
下图所示为一般地代理模式类图,实际上,代理proxy代替具体对象向客户暴露接口,让客户直接通过代理对象调用真实对象的方法。形象地来说,比如买飞机票的时候,你不必非得去机场售票处买票,可以在市区酒店乃至街道上各个代理售票点购买机票,而代售点,就是机场售票中心的代理。乘客调用代售点的“售卖机票”的功能,由于代售点和机场票务中心是联网的,所以你获得了机场官方承认的有效机票。
图1.1 代理模式类图
1.2 静态代理与动态代理的区别
上面用了现实生活中的例子解释了一下代理的基本意义,那么在程序中,有静态代理和动态代理之分,他们的区别是什么?首先,我们用最简单的静态代理的例子来说明什么是静态代理。代码如下。
1 import java.util.Random; 2 3 public class StaticProxyDemo { 4 public static void main(String[] args) { 5 TicketStation ticketStation = new ProxyTicketStation(new OfficalTicketStation()); 6 ticketStation.buyTicket(); 7 } 8 9 } 10 11 /** 12 * 机票对象,每张机票有一个id编号 13 */ 14 class Ticket { 15 private final int id; 16 public Ticket(int id) { 17 this.id = id; 18 } 19 20 public String toString() { 21 return "Ticket:" + id; 22 } 23 } 24 25 26 /** 27 * 机票销售接口 28 */ 29 interface TicketStation { 30 Ticket buyTicket(); 31 } 32 33 34 /** 35 * 机场官方售票大厅, 实现机场销售接口 36 */ 37 class OfficalTicketStation implements TicketStation { 38 private static Random rand = new Random(47); 39 40 public Ticket buyTicket() { 41 return new Ticket(rand.nextInt(100)); 42 } 43 } 44 45 46 /** 47 * 代理售票点,持有一个官方售票大厅的引用 48 * 表示两者之间是联网的 49 */ 50 class ProxyTicketStation implements TicketStation { 51 52 private final TicketStation ticketStation; 53 public ProxyTicketStation(TicketStation station) { 54 ticketStation = station; 55 } 56 57 /** 58 * 代理售票点实际调用官方售票大厅的方法获得机票 59 */ 60 public Ticket buyTicket() { 61 System.out.println("连接机场票务中心...."); 62 Ticket ticket = ticketStation.buyTicket(); 63 if (ticket != null) { 64 System.out.println("您购买的机票编号为: " + ticket); 65 } 66 return ticket; 67 } 68 }
我们直接从main方法中看到创建代理对象的过程:
TicketStation ticketStation = new ProxyTicketStation(new OfficalTicketStation());
1) 创建真实对象:new OfficalTicketStation()
2) 创建代理对象,将真实对象作为构造函数传入
以上两部就是创建静态代理对象的一般步骤,很简单,也很容易理解。之所以能够这样new出一个代理对象,是因为ProxyTicketStation的字节码文件在编译期就已经编译好了,于是,第一次访问该类的静态成员时(构造器也是静态成员),JVM的类加载器就将ProxyTicketStation.class文件加载之后生成了Class对象存入堆内存中,这样就可以通过该类的Class对象创建具体实例了。这里之所以叫静态代理,原因就是ProxyTicketStation.class这个字节码文件,是编译器读取你的源码(ProxyTicketStation.java)生成的并且存放在了磁盘上,你的程序跑起来之后,她只是安静地等待运行过程中需要时被加载。
那么相对的,动态代理就是程序运行时, 由特定程序而不是编译器,动态地写出来的类的字节码(或者通过远程传入的),然后加载实例产生的代理对象。通过Javassist等开源框架,程序员们可以方便的在程序中生成字节码,当然,Java底层天然就可以写,只不过相对复杂,而动态代理对象的字节码是Java底层写的。
2. 动态代理的实现原理
这里涉及到的JVM虚拟机运行的原理,我就通过这张图简要地说明一下。注意,这里省略了验证、解释等过程。
图2.1 Java编译加载字节码文件
上节说过,动态代理中,代理对象的字节码文件,是通过Java底层代码写的,那么具体是怎么写的呢?首先,就需要了解一下动态代理使用的三个步骤。
2.1 动态代理实现三个步骤
第一步: 实现InvocationHandler接口
第二步:创建动态代理对象
第三步:通过代理对象调用方法
简单地代码实现如下:
实现InvocationHandler,该类的作用是,对于稍后动态代理类的所有方法的调用,都会重定向到InvocationHandler的invoke方法。在invoke方法中,可以统一处理非业务逻辑,比如记录对象调用的日志等。就是说,把非业务逻辑和业务逻辑分离了。而且自己实现的InvocationHandler可以复用,供不同proxy对象重定向调用invoke,就减少了需要写的类,是整体代码更加紧凑,易于维护。相反,静态代理类如果数量过多,就会使整体代码显得臃肿和零散,难以维护。
1 import java.lang.reflect.InvocationHandler; 2 import java.lang.reflect.Method; 3 import java.util.stream.Stream; 4 5 public class MyInvocationHandler implements InvocationHandler { 6 private Object proxied; 7 8 public MyInvocationHandler(Object proxied) { 9 this.proxied = proxied; 10 } 11 12 13 public Object invoke(Object proxy, Method method, Object[] args) { 14 System.out.println("Invoke method: " + method.getName() + ", args: " + args); 15 if (args != null) 16 Stream.of(args).forEach(System.out::println); 17 try { 18 if (method.getName().equals("buyTicket")) { 19 System.out.println("Buy ticket...."); 20 Ticket result = (Ticket) method.invoke(proxied, args); 21 System.out.println(result); 22 } 23 } catch (Throwable t) { 24 System.err.println(t); 25 } 26 27 return null; 28 } 29 }
创建代理对象以及调用代理对象方法。创建代理对象proxy时,需要传入三个参数。第一个参数是类加载器,类加载器用于加载代理类字节码文件,一般地,使用当前环境的类加载器即可,如果字节码来自网络或者磁盘,就需要自己实现类加载器了,为什么?因为默认的三个类加载器(启动类加载器,扩展类加载器和系统类加载器)只能加载位于特定目录和classpath下的字节码文件.class。第二个参数是动态创建的代理类,需要实现哪些接口,这里仅需要使用TicketStation接口即可。第三个参数是InvocationHandler对象,这里传入我们实现的handler,这是最重要的参数,因为Java底层在生成代理类的时候,在其每个方法调用中,都会将调用重定向到handler的invoke方法。
1 import java.lang.reflect.Proxy; 2 3 public class DynamicTicketProxyDemo { 4 public static void main(String[] args) { 5 // 创建代理对象 6 TicketStation official = new OfficalTicketStation(); 7 TicketStation proxy = (TicketStation) Proxy.newProxyInstance( 8 TicketStation.class.getClassLoader(), // 类加载器 9 new Class[]{TicketStation.class}, // 代理类需要实现的接口 10 new MyInvocationHandler(official)); // InvocationHandler,对代理所有方法的调用都会重定向到该类invoke方法 11 12 // 调用代理对象方法 13 proxy.buyTicket(); 14 } 15 }
2.2 代理对象是怎样创建的
通过2.1节,我们已经知道了怎样在代码中写动态代理,毫无疑问,最重要的一步是 Proxy.newProxyInstance()方法的调用,理解了这个方法的实现,也就能对动态代理有更深入的理解,那么本节就从源码来了解一下Java动态代理的实现。
1 public static Object newProxyInstance(ClassLoader loader, 2 Class<?>[] interfaces, 3 InvocationHandler h) 4 throws IllegalArgumentException 5 { 6 Objects.requireNonNull(h); // invocationHandler必须传 7 8 final Class<?>[] intfs = interfaces.clone(); 9 final SecurityManager sm = System.getSecurityManager(); 10 if (sm != null) { 11 checkProxyAccess(Reflection.getCallerClass(), loader, intfs); 12 } 13 14 /* 15 * Look up or generate the designated proxy class. 16 */ 17 Class<?> cl = getProxyClass0(loader, intfs); // 1.核心方法,获取动态代理的类对象 18 19 /* 20 * Invoke its constructor with the designated invocation handler. 21 */ 22 try { 23 if (sm != null) { 24 checkNewProxyPermission(Reflection.getCallerClass(), cl); 25 } 26 27 final Constructor<?> cons = cl.getConstructor(constructorParams); // 2.获取带参数的构造器 28 final InvocationHandler ih = h; 29 if (!Modifier.isPublic(cl.getModifiers())) { 30 AccessController.doPrivileged(new PrivilegedAction<Void>() { 31 public Void run() { 32 cons.setAccessible(true); 33 return null; 34 } 35 }); 36 } 37 return cons.newInstance(new Object[]{h}); // 2.创建代理对象并返回 38 } catch (IllegalAccessException|InstantiationException e) { 39 throw new InternalError(e.toString(), e); 40 } catch (InvocationTargetException e) { 41 Throwable t = e.getCause(); 42 if (t instanceof RuntimeException) { 43 throw (RuntimeException) t; 44 } else { 45 throw new InternalError(t.toString(), t); 46 } 47 } catch (NoSuchMethodException e) { 48 throw new InternalError(e.toString(), e); 49 } 50 }
上述方法做的事情就是:1.必要校验; 2. 创建代理对象的Class对象; 3. 调用Constructor.newInstance来创建代理对象。
最重要的一步就是Class<?> cl = getProxyClass0(loader, intfs),该方法源代码如下: 1 private static Class<?> getProxyClass0(ClassLoader loader,
1 private static Class<?> getProxyClass0(ClassLoader loader, 2 Class<?>... interfaces) { 3 if (interfaces.length > 65535) { 4 throw new IllegalArgumentException("interface limit exceeded"); 5 } 6 7 // 尝试从缓存中获取代理对象的类对象,如果没有, 8 // 就调用ProxyClassFactory工厂方法去创建代理对象的Class对象 9 return proxyClassCache.get(loader, interfaces); 10 }
getProxyClass0()方法中,proxyClassCache缓存在Proxy类加载时就初始化了,作为Proxy类的常量定义:
1 public class Proxy implements java.io.Serializable { 2 private static final WeakCache<ClassLoader, Class<?>[], Class<?>> 3 proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory()); 4 5 }
可以看到,proxyClassCache是一个WeakCache缓存对象, 实例化时就传入了ProxyClassFactory代理对象类工厂实例,这样的话,当调用proxyClassCache.get(loader, interfaces),如果缓存中不存在满足当前interfaces的类对象,就会调用ProxyClassFactory工厂来创建代理对象的类对象,ProxyClassFactory嵌入在Proxy的类,它实现了参数化函数接口BiFunction<ClassLoader, Class<?>[], Class<?>>,即通过传入两个参数-----这里是loader和interfaces-----来获得代理类的Class对象,源代码如下,这里省略了部分代码,值保留最核心的部分:
1 private static final class ProxyClassFactory 2 implements BiFunction<ClassLoader, Class<?>[], Class<?>> 3 { 4 // 代理对象的名称前缀 5 private static final String proxyClassNamePrefix = "$Proxy"; 6 7 // 原子操作,每创建一个代理对象,就会在前缀后加上一个数字以区分 8 private static final AtomicLong nextUniqueNumber = new AtomicLong(); 9 10 @Override 11 public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) { 12 13 String proxyPkg = null; // 要创建代理类的包 14 int accessFlags = Modifier.PUBLIC | Modifier.FINAL; //这里都是public final的
15 // 这里是组装代理类完整的包名 16 for (Class<?> intf : interfaces) { 17 int flags = intf.getModifiers(); 18 if (!Modifier.isPublic(flags)) { 19 accessFlags = Modifier.FINAL; 20 String name = intf.getName(); 21 int n = name.lastIndexOf('.'); 22 String pkg = ((n == -1) ? "" : name.substring(0, n + 1)); 23 if (proxyPkg == null) { 24 proxyPkg = pkg; 25 } else if (!pkg.equals(proxyPkg)) { 26 throw new IllegalArgumentException( 27 "non-public interfaces from different packages"); 28 } 29 } 30 } 31 32 if (proxyPkg == null) { 33 proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; 34 } 35 36 /* 37 * 组装出代理类的完整限定名 38 */ 39 long num = nextUniqueNumber.getAndIncrement(); 40 String proxyName = proxyPkg + proxyClassNamePrefix + num; 41 42 /* 43 * 最终要的代码,生成代理类的字节码 44 */ 45 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( 46 proxyName, interfaces, accessFlags); 47 try { 48 return defineClass0(loader, proxyName, 49 proxyClassFile, 0, proxyClassFile.length); 50 } catch (ClassFormatError e) { 51 /* 52 * A ClassFormatError here means that (barring bugs in the 53 * proxy class generation code) there was some other 54 * invalid aspect of the arguments supplied to the proxy 55 * class creation (such as virtual machine limitations 56 * exceeded). 57 */ 58 throw new IllegalArgumentException(e.toString()); 59 } 60 } 61 }
上述代码中,最重要的一步就是byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); 这一步是调用ProxyGenerator来生成代理类字节码文件,如同Javaassist框架可以方便地生成类字节码一样,ProxyGenerator封装了底层代码来生成代理类字节码。回忆一下JVM加载过程,通常情况下,JVM类记载器运行时加载的是编译期生成并保存在磁盘特定路径中的.class文件,既然编译器能生成.class字节码文件,Java当然能够支持程序员自己写程序在程序运行时生成.class字节码文件喽,这里ProxyGenerator.generateProxyClass就是Java实现的这样一种过程,只不过封装了复杂的底层逻辑。我们姑且只需要知道ProxyGenerator.generateProxyClass生成了代理类的.class文件即可。
既然生成了.class字节码文件,那么接下来呢?自然是加载并初始化这个类,这一过程就是调用类加载器将.class文件读入Java虚拟机内存中, 经过一系列的验证,内存分配,解析之后,初始化该类,即执行静态代码块或者初始化静态成员。这一过程,就是Proxy类中这个方法执行的:
private static native Class<?> defineClass0(ClassLoader loader, String name, byte[] b, int off, int len);
我们看到这个方法时native的,表示加载验证初始化代理类的过程是更底层的实现,这里就不必继续深究下去了。总之,该方法会返回给我们一个代理类的Class对象。跳转到Proxy.newProxyInstance方法源码中去,接下来就是获取构造器对象,然后通过构造器的newInstance方法,生成代理对象实例,最终返回给我们。值得注意的是,这里获取的构造器 final Constructor<?> cons = cl.getConstructor(constructorParams)是带参数constructorParams的构造器,该包括该构造器代理类是由 ProxyGenerator.generateProxyClass()方法写的,constructorParams参数在Proxy类中有定义:
1 public class Proxy implements java.io.Serializable { 2 3 /** parameter types of a proxy class constructor */ 4 private static final Class<?>[] constructorParams = 5 { InvocationHandler.class }; 6 }
即,代理对象的构造器参数是需要传入InvocationHandler实例,这也是为什么Proxy.newProxyInstance()方法必须传入InvocationHandler对象的原因,最后调用return cons.newInstance(new Object[]{h});方法,其中h就是我们传入的InvocationHandler对象, 通过这个构造器传入的InvocationHandler对象,生成的代理对象,被调用任何方法时,都会重定向到InvocatoinHandler.invoke()方法上去,这都是底层写的字节码来实现的。
还有一点,就是在生成代理类的字节码时候,所有方法都是public final修饰的,即生成的代理类是不能重写。