1. 前言
JDK 动态代理的应用还是非常广泛的,例如在 Spring、MyBatis 以及 Feign 等很多框架中动态代理都被大量的使用,可以说学好 JDK 动态代理,对于我们阅读这些框架的底层源码还是很有帮助的
2. 一个简单的例子
在分析原因之前,我们先完整的看一下实现 JDK 动态代理需要几个步骤,首先需要定义一个接口
2.1. 定义接口
public interface Worker { void work(); }
2.2. 接口实现类
public class Programmer implements Worker { @Override public void work() { System.out.println("coding..."); } }
2.3. 自定义 Handler
自定义一个 Handler,实现 InvocationHandler 接口,通过重写内部的 invoke() 方法实现逻辑增强
public class WorkHandler implements InvocationHandler { private final Object target; public WorkHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("work")) { System.out.println("before work..."); Object result = method.invoke(target, args); System.out.println("after work..."); return result; } return method.invoke(target, args); } }
2.4. 测试
在 main() 方法中进行测试,使用 Proxy 类的静态方法 newProxyInstance() 生成一个代理对象并调用方法
public class MainTest { public static void main(String[] args) { Programmer programmer = new Programmer(); Worker worker = (Worker) Proxy.newProxyInstance( programmer.getClass().getClassLoader(), programmer.getClass().getInterfaces(), new WorkHandler(programmer)); worker.work(); } }
2.5. 输出结果
before work...
coding...
after work...
3. 源码分析
既然是一个代理的过程,那么肯定存在原生对象和代理对象之分,下面我们查看源码中是如何动态的创建代理对象的过程。
上面例子中,创建代理对象调用的是 Proxy 类的静态方法 newProxyInstance(),查看一下源码
3.1. newProxyInstance() 方法
public class Proxy implements java.io.Serializable { protected InvocationHandler h; // 有参构造器,参数是 InvocationHandler protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; } @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // 如果h为空直接抛出空指针异常,之后所有的单纯的判断null并抛异常,都是此方法 Objects.requireNonNull(h); // 拷贝类实现的所有接口 final Class>[] intfs = interfaces.clone(); // 获取当前系统安全接口 final SecurityManager sm = System.getSecurityManager(); if (sm != null) { // Reflection.getCallerClass 返回调用该方法的方法的调用类;loader:接口的类加载器 // 进行包访问权限、类加载器权限等检查 checkProxyAccess(Reflection.getCallerClass(), loader, intfs); } // 查找或生成指定的代理类 Class> cl = getProxyClass0(loader, intfs); // 用指定的调用处理程序调用它的构造函数 try { if (sm != null) { checkNewProxyPermission(Reflection.getCallerClass(), cl); } // 获取代理类的构造函数对象 // constructorParams是类常量,作为代理类构造函数的参数类型,常量定义如下: // private static final Class>[] constructorParams = { InvocationHandler.class }; final Constructor> cons = cl.getConstructor(constructorParams); final InvocationHandler ih = h; if (!Modifier.isPublic(cl.getModifiers())) { AccessController.doPrivileged(new PrivilegedAction() { public Void run() { cons.setAccessible(true); return null; } }); } // 根据代理类的构造函数对象来创建需要返回的代理类对象 return cons.newInstance(new Object[]{h}); } // 省略 catch...... } }
- 在 checkProxyAccess() 方法中,进行参数验证
- 在 getProxyClass0() 方法中,生成一个代理类 Class 或寻找已生成过的代理类的缓存
- 通过 getConstructor() 方法获取生成的代理类的构造方法
- 通过 newInstance() 方法,生成最终的代理对象
上面这个过程中,获取构造方法和生成代理对象都是利用的 Java 中的反射机制,而需要重点看的是生成代理类的方法 getProxyClass0()
3.1.1. getProxyClass0() 方法
private static Class> getProxyClass0(ClassLoader loader, Class>... interfaces) { // 接口数不得超过 65535 个,这么大,足够使用的了 if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } // 如果缓存中有代理类了直接返回,否则将由代理类工厂ProxyClassFactory创建代理类 return proxyClassCache.get(loader, interfaces); }
如果缓存中已经存在了就直接从缓存中取,这里的 proxyClassCache 是一个 WeakCache 类型,如果缓存中目标 classLoader 和接口数组对应的类已经存在,那么返回缓存的副本。如果没有就使用 ProxyClassFactory 去生成 Class 对象
3.1.1.1. get() 方法
// key:类加载器;parameter:接口数组 public V get(K key, P parameter) { // 检查指定类型的对象引用不为空null。当参数为null时,抛出空指针异常 Objects.requireNonNull(parameter); // 清除已经被 GC 回收的弱引用 expungeStaleEntries(); // 将ClassLoader包装成CacheKey, 作为一级缓存的key Object cacheKey = CacheKey.valueOf(key, refQueue); // 获取得到二级缓存 ConcurrentMap
很明显,重点关注下面代码
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
3.1.1.1.1. apply() 方法
private static final class ProxyClassFactory implements BiFunction[], Class>> { // 代理类的前缀名都以 $Proxy 开始 private static final String proxyClassNamePrefix = "$Proxy"; // 使用唯一的编号给作为代理类名的一部分,如 $Proxy0,$Proxy1 等 private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class> apply(ClassLoader loader, Class>[] interfaces) { Map , Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length); for (Class> intf : interfaces) { // 验证指定的类加载器(loader)加载接口所得到的Class对象(interfaceClass)是否与intf对象相同 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"); } // 验证该Class对象是不是接口 if (!interfaceClass.isInterface()) { throw new IllegalArgumentException( interfaceClass.getName() + " is not an interface"); } // 验证该接口是否重复 if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { throw new IllegalArgumentException( "repeated interface: " + interfaceClass.getName()); } } // 声明代理类所在包 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 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"); } } } // 1.根据规则生成文件名 if (proxyPkg == null) { /*如果都是public接口,那么生成的代理类就在com.sun.proxy包下如果报 java.io.FileNotFoundException: com\sun\proxy\$Proxy0.class (系统找不到指定的路径。)的错误,就先在你项目中创建com.sun.proxy路径*/ proxyPkg = ReflectUtil.PROXY_PACKAGE + "."; } long num = nextUniqueNumber.getAndIncrement(); // 代理类的完全限定类名,如 com.sun.proxy.$Proxy0.calss String proxyName = proxyPkg + proxyClassNamePrefix + num; // 2.生成代理的字节码数组 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); // 3.生成 Class try { return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } } }
在 apply() 方法中,主要做了下面 3 件事
- 根据规则生成文件名
- 利用 ProxyGenerator.generateProxyClass() 方法生成代理的字节码数组
- 调用方法 defineClass0() 生成 Class
3.1.2. getConstructor() 和 newInstance() 方法
返回代理类的 Class 后的流程,获取构造方法和生成代理对象都是利用的 Java 中的反射机制
4. 代理对象长啥样
4.1. 代理对象长啥样
创建代理对象流程的源码分析完了,我们可以先通过 debug 来看看上面生成的这个代理对象究竟是个什么
和源码中看到的规则一样,是一个 Class 为 $Proxy0 的对象。再看一下代理对象的 Class 的详细信息
类的全限定类名是 com.sun.proxy.$Proxy0,在上面我们提到过,这个类是在运行过程中动态生成的
4.2. $Proxy0 反编译
看一下反编译后 $Proxy0.java 文件的内容,下面的代码中,我只保留了核心部分,省略了无关紧要的 equals()、toString()、hashCode() 方法的定义
public final class $Proxy0 extends Proxy implements Worker{ public $Proxy0(InvocationHandler invocationhandler){ super(invocationhandler); } public final void work(){ try{ super.h.invoke(this, m3, null); return; }catch(Error _ex) { } catch(Throwable throwable){ throw new UndeclaredThrowableException(throwable); } } private static Method m3; static { try{ m3 = Class.forName("com.hydra.test.Worker").getMethod("work", new Class[0]); //省略其他Method }//省略catch } }
这个临时生成的代理类 $Proxy0 中主要做了下面的几件事
- 在这个类的静态代码块中,通过 反射机制 初始化了多个静态方法 Method 变量,除了接口中的方法还有 equals()、toString()、hashCode() 这三个方法
- 代理类 $Proxy0 继承了父类 Proxy,在其实例化的过程中会调用父类的构造器,而父类 Proxy 中的构造器中传入的 InvocationHandler 对象实际上是我们自定义的 WorkHandler 的实例。此时就可以调用 WorkHandler 的 invoke() 方法了
- 同时,代理类 $Proxy0 也实现了自定义的接口 Worker,并重写了 work() 方法,在 work()方法内又调用了 InvocationHandler 的 invoke() 方法,也就是实际上调用了 WorkHandler 的 invoke() 方法
到这里,整体的流程就分析完了,我们可以用一张图来简要总结上面的过程
5. JDK 动态代理为什么要有接口
其实如果不看上面的分析,我们也应该知道,要扩展一个类有常见的两种方式,继承父类或实现接口。这两种方式都允许我们对方法的逻辑进行增强,但现在不是由我们自己来重写方法,而是要想办法让 JVM 去调用 InvocationHandler 中的 invoke() 方法,也就是说代理类需要和两个东西关联在一起
- 被代理类
- InvocationHandler
而 JDK 动态代理处理这个问题的方式是选择继承父类 Proxy,并把 InvocationHandler 保存在父类的对象中
public class Proxy implements java.io.Serializable { protected InvocationHandler h; protected Proxy(InvocationHandler h) { Objects.requireNonNull(h); this.h = h; } // ...... }
通过父类 Proxy 的构造方法,保存了创建代理对象过程中传进来的 InvocationHandler 的实例,使用 protected 修饰保证了它可以在子类中被访问和使用。
但是同时,因为 Java 是单继承的,因此在代理类 $Proxy0 继承了 Proxy 后,其只能通过实现目标接口的方式来实现方法的扩展,达到我们增强目标方法逻辑的目的
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。