动态代理的简介
Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象,便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能,这是一套非常灵活有弹性的代理框架。Java动态代理实际上通过反射技术,把代理对象和被代理对象(真实对象)的代理关系建立延迟到程序运行之后,动态创建新的代理类去完成对真实对象的代理操作(可以改变原来真实对象的方法行为),这一点成为了当前主流的AOP框架和延迟加载功能的基础。本文在查看和编写动态代理相关的代码使用的是JDK11,不过JDK动态代理相关的功能和接口已经相对稳定,不必担心JDK版本升级带来的兼容性问题,但是需要注意由于JDK9引入了模块概念,动态代理的源码也有不少的改动。下文先介绍设计模式中的代理模式,接着会分析JDK动态代理的核心类库、流程和机制,最后分析其底层源码级别实现。
设计模式中的代理模式
代理模式是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
代理模式主要包括三种角色:
- Subject抽象主题角色:一般定义为抽象类或者接口,是作为功能的定义,提供一系列抽象的功能方法。
- RealSubject具体(真实)主题角色:一般称为被委托角色或者被代理角色,它是Subject的一个具体实现。
- ProxySubject代理主题角色:一般称为委托角色或者代理角色,一般ProxySubject也实现(或者继承)Subject,接收一个具体的Subject实例RealSubject,在RealSubject处理前后做预定义或者后置操作,甚至可以直接忽略RealSubject原来的方法。
把上面的类图编写成代码如下:
public interface Subject {
void doSomething(); }
public class RealSubject implements Subject {
@Override public void doSomething() { System.out.println("RealSubject doSomething..."); } }
public class ProxySubject implements Subject {
private final Subject subject;
public ProxySubject(Subject subject) { this.subject = subject; }
@Override public void doSomething() { subject.doSomething(); doOtherThing(); }
private void doOtherThing() { System.out.println("ProxySubject doOtherThing..."); } }
public class Client {
public static void main(String[] args) throws Exception { Subject subject = new RealSubject(); ProxySubject proxySubject = new ProxySubject(subject); proxySubject.doSomething(); } }
|
运行Client#main()
输出:
RealSubject doSomething... ProxySubject doOtherThing...
|
代理模式在日常的场景中也经常碰到,比较常见的一个场景就是游戏代练,套进去上面的代码可以写个比较生动的例子:
public interface Player {
void playGame(); }
public class I implements Player {
@Override public void playGame() { System.out.println("操作Throwable游戏角色打怪升级"); } }
public class ProxyPlayer implements Player {
private final Player player;
public ProxyPlayer(Player player) { this.player = player; }
@Override public void playGame() { login(); this.player.playGame(); logout(); }
private void login() { System.out.println("登录Throwable游戏角色"); }
private void logout() { System.out.println("退出Throwable游戏角色"); } }
|
代理模式有几个比较大的优点:
- 职责清晰:也就是真实主题角色只需要实现具体的逻辑,不需关注代理类的职责,而代理类也只需要处理预处理和后置的逻辑,类的职责分明。
- 高扩展性:由于职责分明,也就是真实主题角色可以随时修改实现,这样就能通过更新或者替换真实主题的实现并且不改变代理主题角色的情况下改变具体功能。
- 高灵活性:主要体现在后面提到的动态代理。
JDK动态代理的核心API
JDK动态代理提供外部使用的主要依赖两个类:
java.lang.reflect.Proxy
:可以理解为代理类的工厂类(其实也是父类,见下文)。
java.lang.reflect.InvocationHandler
:代理实例需要实现的调用处理器接口。
Proxy
java.lang.reflect.Proxy
是JDK动态代理的核心类,它的核心功能是提供静态方法来为一组接口动态地生成代理类并且返回代理实例对象,类似于代理类实例的工厂类。java.lang.reflect.Proxy
主要提供四个public静态方法:
public static InvocationHandler getInvocationHandler(Object proxy)
public static Class<?> getProxyClass(ClassLoader loader, Class<?>[] interfaces)
public static boolean isProxyClass(Class<?> cl)
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
|
InvocationHandler getInvocationHandler(Object proxy)
:通过制定的代理类实例查找它关联的调用处理器实例。
Class<?> getProxyClass(ClassLoader loader, Class<?>[] interfaces)
:用于获取关联于指定类装载器和一组接口的动态代理类的类对象,也就是获取$ProxyXXX
的类型,此方法在JDK9以后标记为过期,原因是:在命名模块中生成的代理类是封闭的,模块外的代码无法访问这些类(违反模块规则调用了会抛异常)。
boolean isProxyClass(Class<?> cl)
:用于判断指定类是否是一个动态代理类。
Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
:这个是JDK动态代理最核心的方法,用于为指定类装载器、一组接口及调用处理器生成动态代理类实例,也就是生成$ProxyXXX
的实例。此方法需要指定类加载器java.lang.ClassLoader
,Proxy静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是在运行时动态生成的而非预存在于任何一个.class文件中。interfaces是Class数组,也就是需要使用InvocationHandler进行代理访问的接口类型数组,这里的h参数就是调用处理器的实例。
InvocationHandler
java.lang.reflect.InvocationHandler
是调用处理器接口,它自定义了一个invoke
方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
public interface InvocationHandler { Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
|
参数说明:
- proxy:Object类型,此参数即是代理类实例,也就是
$ProxyXXX
的实例。
- method:
java.lang.reflect.Method
类型,被调用的方法的实例。
- args:Object[]类型,被调用方法的参数数组。
实现java.lang.reflect.InvocationHandler
接口,通过实现invoke
方法即可添加代理访问的逻辑,在这个逻辑代码块中除了可以调用委托类的方法,还可以织入额外的自定义逻辑,AOP就是这样实现的。
JDK动态代理的流程
JDK动态代理的使用流程如下:
- 1、通过实现
java.lang.reflect.InvocationHandler
接口创建自定义的调用处理器。
- 2、通过为
java.lang.reflect.Proxy
类指定ClassLoader
对象和一组interface来创建动态代理类。
- 3、通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型。
- 4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
伪代码如下:
InvocationHandler handler = new InvocationHandlerImpl(..);
Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... });
Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class });
Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
|
上面的过程比较复杂,可以进行精简。简化后的伪代码如下:
InvocationHandler handler = new InvocationHandlerImpl(..);
Interface proxy = (Interface) Proxy.newProxyInstance(classLoader, new Class[] { Interface.class }, handler);
|
JDK动态代理的机制
首先是JDK动态代理生成的代理类本身的特点:
- 1、包(或者JDK9引入的模块):如果所代理的接口都是public的,那么它将被定义在包
com.sun.proxy
;如果所代理的接口中有非public的接口(因为接口不能被定义为protect或private,所以除public之外就是默认的package访问级别,修饰符为default),那么它将被定义在该接口所在包(假设代理了throwable.club
包中的某非public接口A,那么新生成的代理类所在的包就是throwable.club
),值得注意的是,如果接口数组中存在非public的接口,那么它们必须在同一个包路径下,否则会抛异常。这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问。
- 2、类修饰符:该代理类具有final和public修饰符,意味着它可以被所有的类访问,但是不能被再度继承。
- 3、类名:代理类名称格式是
$ProxyN
,其中N是一个逐一递增的阿拉伯数字,代表java.lang.reflect.Proxy
类第N次生成的动态代理类,值得注意的一点是,并不是每次调用Proxy的静态方法创建动态代理类都会使得N值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会从缓存中获取先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类,这样可以节省不必要的代码重复生成,提高了代理类的创建效率。
- 4、类继承关系:代理类的继承关系图如下:
由图可知,java.lang.reflect.Proxy
类是代理类的父类,这个规则适用于所有由java.lang.reflect.Proxy
创建的动态代理类。而且该类还实现了其所代理的一组接口,这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。
代理类实例的特点
每个代理类实例都会关联一个调用处理器对象,可以通过java.lang.reflect.Proxy
提供的静态方法getInvocationHandler()
去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时,这些方法最终都会由调用处理器的 invoke 方法执行,此外,值得注意的是,代理类的根类java.lang.Object
中有三个方法也同样会被分派到调用处理器的invoke
方法执行,它们是hashCode
、equals
和toString
,可能的原因有:
- 一、因为这些方法为public且非final类型,能够被代理类覆盖。
- 二、因为这些方法往往呈现出一个类的某种特征属性,具有一定的区分度,所以为了保证代理类与委托类对外的一致性,这三个方法也应该被分派到委托类执行。当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器,而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用,因为在代理类内部无法区分其当前的被引用类型。
被代理的一组接口的特点
首先,要注意不能有重复的接口,以避免动态代理类代码生成时的编译错误。其次,这些接口对于类装载器必须可见,否则类装载器将无法链接它们,将会导致类定义失败。再次,需被代理的所有非public的接口必须在同一个包中,否则代理类生成也会失败。最后,接口的数目不能超过65535,这是JVM设定的限制,这一点在代理类生成的时候也做了判断。
异常处理
从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常,因为所有的异常都继承于Throwable接口,但事实是否如此呢?答案是否定的,原因是我们必须遵守一个继承原则:即子类覆盖父类或实现父接口的方法时,抛出的异常必须在原方法支持的异常列表之内。所以虽然调用处理器理论上讲能够,但实际上往往受限制,除非父接口中的方法支持抛Throwable异常。那么如果在invoke方法中的确产生了接口方法声明中不支持的异常,那将如何呢?放心,Jdk动态代理类已经为我们设计好了解决方法:它将会抛出UndeclaredThrowableException
异常。这个异常是一个RuntimeException
类型,所以不会引起编译错误。通过该异常的getCause
方法,还可以获得原来那个不受支持的异常对象,以便于错误诊断。
JDK动态代理源码分析
因为JDK动态代理核心逻辑都在java.lang.reflect.Proxy
类中,下面简单分析一下这个类的源码。先看Proxy
类中的几个重要的静态变量:
private static final String PROXY_PACKAGE_PREFIX = ReflectUtil.PROXY_PACKAGE;
private static final Class<?>[] constructorParams = { InvocationHandler.class };
private static final ClassLoaderValue<Constructor<?>> proxyCache = new ClassLoaderValue<>();
|
这里注意到ClassLoaderValue
,下文会调用到它的一个很复杂的调用链:
return proxyCache.sub(intf).computeIfAbsent( loader, (ld, clv) -> new ProxyBuilder(ld, clv.key()).build() );
public V computeIfAbsent(ClassLoader cl, BiFunction<? super ClassLoader,? super CLV,? extends V> mappingFunction) throws IllegalStateException {
|
上面的computeIfAbsent
中使用了函数式接口和Lambda表达式,如果Lambda表达式玩的比较熟练看起来应该没问题,它的功能可以解读为:通过接口类型和类加载器实例计算通过接口类型和类加载器实例构建ProxyBuilder
实例并且调用ProxyBuilder#build()
得到的结果,如果结果已经存在则直接返回缓存。其实computeIfAbsent
在Map
接口中也定义了同样的方法,功能是相似的。
接着看Proxy
的构造函数:
protected InvocationHandler h;
private Proxy() { }
protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; }
|
到此可以明确一点,既然所有动态代理类都是java.lang.reflect.Proxy
的子类,那么它们一定具备一个包含InvocationHandler
参数的构造器。接着查看``方法的源码:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) { Objects.requireNonNull(h); final Class<?> caller = System.getSecurityManager() == null ? null : Reflection.getCallerClass(); Constructor<?> cons = getProxyConstructor(caller, loader, interfaces); return newProxyInstance(caller, cons, h); }
|
先看getProxyConstructor
方法:
private static Constructor<?> getProxyConstructor(Class<?> caller, ClassLoader loader, Class<?>... interfaces){ if (interfaces.length == 1) { Class<?> intf = interfaces[0]; if (caller != null) { checkProxyAccess(caller, loader, intf); } return proxyCache.sub(intf).computeIfAbsent( loader, (ld, clv) -> new ProxyBuilder(ld, clv.key()).build() ); } else { final Class<?>[] intfsArray = interfaces.clone(); if (caller != null) { checkProxyAccess(caller, loader, intfsArray); } final List<Class<?>> intfs = Arrays.asList(intfsArray); return proxyCache.sub(intfs).computeIfAbsent( loader, (ld, clv) -> new ProxyBuilder(ld, clv.key()).build() ); } }
|
可以明确,核心的逻辑都交给了Proxy
的内部类ProxyBuilder
完成,先看ProxyBuilder
的静态成员变量:
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
private static final String proxyClassNamePrefix = "$Proxy";
private static final AtomicLong nextUniqueNumber = new AtomicLong();
private static final ClassLoaderValue<Boolean> reverseProxyCache = new ClassLoaderValue<>();
|
ProxyBuilder(ClassLoader loader, Class<?> intf) { this(loader, Collections.singletonList(intf)); }
ProxyBuilder(ClassLoader loader, List<Class<?>> interfaces) { if (!VM.isModuleSystemInited()) { throw new InternalError("Proxy is not supported until " + "module system is fully initialized"); } if (interfaces.size() > 65535) { throw new IllegalArgumentException("interface limit exceeded: " + interfaces.size()); } Set<Class<?>> refTypes = referencedTypes(loader, interfaces);
validateProxyInterfaces(loader, interfaces, refTypes);
this.interfaces = interfaces; this.module = mapToModule(loader, interfaces, refTypes); assert getLoader(module) == loader; }
|
一个构造器处理的逻辑也是相对复杂,主要是因为引入模块管理的概念,接着看ProxyBuilder#build()
的源码:
Constructor<?> build() { Class<?> proxyClass = defineProxyClass(module, interfaces); final Constructor<?> cons; try { cons = proxyClass.getConstructor(constructorParams); } catch (NoSuchMethodException e) { throw new InternalError(e.toString(), e); } AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { cons.setAccessible(true); return null; } }); return cons; }
|
最后到逻辑最复杂的代理类的生成过程ProxyBuilder#defineProxyClass()
:
private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) { String proxyPkg = null; int accessFlags = Modifier.PUBLIC | Modifier.FINAL; for (Class<?> intf : interfaces) { int flags = intf.getModifiers(); if (!Modifier.isPublic(flags)) { accessFlags = Modifier.FINAL; String pkg = intf.getPackageName(); if (proxyPkg == null) { proxyPkg = pkg; } else if (!pkg.equals(proxyPkg)) { throw new IllegalArgumentException( "non-public interfaces from different packages"); } } } if (proxyPkg == null) { proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName() : PROXY_PACKAGE_PREFIX; } else if (proxyPkg.isEmpty() && m.isNamed()) { throw new IllegalArgumentException( "Unnamed package cannot be added to " + m); } if (m.isNamed()) { if (!m.getDescriptor().packages().contains(proxyPkg)) { throw new InternalError(proxyPkg + " not exist in " + m.getName()); } } long num = nextUniqueNumber.getAndIncrement(); String proxyName = proxyPkg.isEmpty() ? proxyClassNamePrefix + num : proxyPkg + "." + proxyClassNamePrefix + num; ClassLoader loader = getLoader(m); trace(proxyName, m, loader, interfaces); byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags); try { Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile, 0, proxyClassFile.length, loader, null); reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE); return pc; } catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString()); } }
|
到这一步为止,代理类的生成过程已经大致分析完毕,ProxyGenerator
中涉及到大量字节码操作,这里就不深入分析了。那么回到最前面的方法,得到代理类和它的构造实例,接着就可以生成代理实例:
private static Object newProxyInstance(Class<?> caller, // null if no SecurityManager Constructor<?> cons, InvocationHandler h) { try { if (caller != null) { checkNewProxyPermission(caller, cons.getDeclaringClass()); } 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); } } }
|
小结一下:
- 接口数组中所有接口元素的类修饰符最好一致为public。如果接口数组中存在非default修饰的接口元素,那么接口数组中的所有接口类都要放在同一个包下,并且都要使用default修饰。
- 很少情况下我们修改接口的修饰符,默认为public,那么所有代理类的包路径都是
com.sun.proxy
,全类名是:com.sun.proxy.$ProxyN
。
- 代理接口数量不能超过65535。
JDK动态代理类的源代码
前面已经分析完了代理类的生成过程,这里举个简单的使用例子,并且观察生成的动态代理类的源代码。
使用例子:
public interface Simple {
void sayHello(String name); }
public class DefaultSimple implements Simple {
@Override public void sayHello(String name) { System.out.println(String.format("%s say hello!", name)); } }
public class Main {
public static void main(String[] args) throws Exception { Simple simple = new DefaultSimple(); Object target = Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{Simple.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Before say hello..."); method.invoke(simple, args); System.out.println("After say hello..."); return null; } }); Simple proxy = (Simple) target; proxy.sayHello("throwable"); } }
|
调用后输出:
Before say hello... throwable say hello! After say hello...
|
可以看到,我们在被代理类DefaultSimple
实例的方法调用前后织入了自定义的逻辑,这就是通过JDK动态代理实现AOP的底层原理。在JDK8中可以直接使用sun.misc.ProxyGenerator
去输出代理类的class文件,但是JDK11中这个代理类生成器已经变成java.lang.reflect.ProxyGenerator
,并且这个类是包私有的,我们无法使用,但是它提供了jdk.proxy.ProxyGenerator.saveGeneratedFiles
这个VM参数让我们可以保存代理类的class文件:
# JVM参数 -Djdk.proxy.ProxyGenerator.saveGeneratedFiles=true
|
配置好VM参数后,再次调用mian方法就能看到在项目的顶层包路径下看到对应的类com.sun.proxy.$Proxy0
,目前从java.lang.reflect.ProxyGenerator
源码看无法控制代理类文件的输出路径,生成的代理类内容如下:
public final class $Proxy0 extends Proxy implements Simple { private static Method m1; private static Method m3; private static Method m2; 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 void sayHello(String var1) throws { try { super.h.invoke(this, m3, 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 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")); m3 = Class.forName("club.throwable.jdk.sample.reflection.proxy.Simple").getMethod("sayHello", Class.forName("java.lang.String")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
|
代理类的代码比较简单,有如下几个特点:
- 1、代理类继承于
java.lang.reflect.Proxy
,实现了接口数组中的接口元素类,构造函数只有一个InvocationHandler
类型的参数。
- 2、接口中的所有被代理方法包括
equals
、toString
、hashCode
都建立了一个对应的Method私有静态实例,在最后面的静态代码块中实例化。
- 3、所有代理方法都是用public final修饰,也就是代理类中的代理方法是不能覆盖的。
- 4、所有代理方法都是通过
InvocationHandler
实例的invoke
方法进行调用的,记得第一个参数是代理类实例本身,如果用了在InvocationHandler#invoke()
方法实现过程中使用了这个参数有可能造成死循环。
小结
诚然,Proxy已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy。Java的单继承机制注定了这些动态代理类们无法实现对class的动态代理(所以只能代理接口,实际上是基于反射对方法级别的逻辑进行编织)。有很多条理由,可以否定对class代理的必要性,但是同样有一些理由,相信支持class动态代理会更美好。但是,不完美并不等于不伟大,伟大是一种本质,JDK动态代理就是佐例。
参考资料:
(本文完 e-20181208 c-3-d)