JDK动态代理为什么必须要基于接口
目录
- 1. 前言
- 2. 一个简单的例子
- 2.1. 定义接口
- 2.2. 接口实现类
- 2.3. 自定义 `Handler`
- 2.4. 测试
- 2.5. 输出结果
- 3. 源码分析
- 3.1. `newProxyInstance()` 方法
- 3.1.1. `getProxyClass0()` 方法
- 3.1.1.1. `get()` 方法
- 3.1.1.1.1. `apply()` 方法
- 3.1.2. `getConstructor()` 和 `newInstance()` 方法
- 4. 代理对象长啥样
- 4.1. 代理对象长啥样
- 4.2. `$Proxy0` 反编译
- 5. `JDK` 动态代理为什么要有接口
1. 前言
JDK
动态代理的应用还是非常广泛的,例如在 Spring、MyBatis
以及 Feign
等很多框架中动态代理都被大量的使用,可以说学好 JDK
动态代理,对于我们阅读这些框架的底层源码还是很有帮助的
2. 一个简单的例子
在分析原因之前,我们先完整的看一下实现 JDK
动态代理需要几个步骤,首先需要定义一个接口
2.1. 定义接口
public interface Worker {void work();
}
2.2. 接口实现类
public class Programmer implements Worker {@Overridepublic 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;}@Overridepublic 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()
方法
@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<Void>() {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, 作为一级缓存的keyObject cacheKey = CacheKey.valueOf(key, refQueue);// 获取得到二级缓存ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);// 没有获取到对应的值if (valuesMap == null) {ConcurrentMap<Object, Supplier<V>> oldValuesMap= map.putIfAbsent(cacheKey,valuesMap = new ConcurrentHashMap<>());if (oldValuesMap != null) {valuesMap = oldValuesMap;}}// 根据代理类实现的接口数组来生成二级缓存keyObject subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));// 通过subKey获取二级缓存值Supplier<V> supplier = valuesMap.get(subKey);Factory factory = null;// 这个循环提供了轮询机制, 如果条件为假就继续重试直到条件为真为止while (true) {if (supplier != null) {// 在这里supplier可能是一个Factory也可能会是一个CacheValue// 在这里不作判断, 而是在Supplier实现类的get方法里面进行验证V value = supplier.get();if (value != null) {return value;}}if (factory == null) {factory = new Factory(key, parameter, subKey, valuesMap);}if (supplier == null) {// 到这里表明subKey没有对应的值, 就将factory作为subKey的值放入supplier = valuesMap.putIfAbsent(subKey, factory);if (supplier == null) {supplier = factory;}// 否则, 可能期间有其他线程修改了值, 那么就不再继续给subKey赋值, 而是取出来直接用} else {// 期间可能其他线程修改了值, 那么就将原先的值替换if (valuesMap.replace(subKey, supplier, factory)) {supplier = factory;} else {supplier = valuesMap.get(subKey);}}}
}
很明显,重点关注下面代码
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
3.1.1.1.1. apply()
方法
private static final class ProxyClassFactoryimplements BiFunction<ClassLoader, Class<?>[], Class<?>> {// 代理类的前缀名都以 $Proxy 开始private static final String proxyClassNamePrefix = "$Proxy";// 使用唯一的编号给作为代理类名的一部分,如 $Proxy0,$Proxy1 等private static final AtomicLong nextUniqueNumber = new AtomicLong();@Overridepublic Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {Map<Class<?>, 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.calssString 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
,实例化的过程中会调用父类的构造方法,构造方法中传入的invocationHandler
对象实际上就是我们自定义的WorkHandler
的实例$Proxy0
实现了自定义的接口Worker
,并重写了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
是单继承的,因此在继承了 Proxy
后,只能通过实现目标接口的方式来实现方法的扩展,达到我们增强目标方法逻辑的目的
JDK动态代理为什么必须要基于接口相关推荐
- JDK动态代理为什么必须要基于接口?
前几天的时候,交流群里的小伙伴抛出了一个问题,为什么JDK的动态代理一定要基于接口实现呢? 好的安排,其实要想弄懂这个问题还是需要一些关于代理和反射的底层知识的,我们今天就盘一盘这个问题,走你~ 一个 ...
- 静态代理,JDK动态代理,Cglib动态代理详解
目录 一.代理模式 二.静态代理 三.动态代理 3.1 JDK动态代理 3.2 Cglib动态代理 四.两种动态代理区别 一.代理模式 代理模式(Proxy Pattern)是程序设计中的一种设计模式 ...
- 谁与争锋,JDK动态代理大战CGLib动态代理
文章目录 一.前言 二.基本概念 三.JDK 和 CGLib动态代理区别 3.1 JDK动态代理具体实现原理 3.2 CGLib动态代理 3.3 两者对比 3.4 使用注意 四.JDK 和 CGLib ...
- 08.jdk动态代理原理
课程标题<jdk动态代理底层原理分析> 课程内容: 1.什么是代理模式 2.代理模式应用场景有哪些 3.代理模式实现方式有哪些 4.静态代理与动态代理区别 5.JDK动态代理原理分析 6. ...
- Java 结合实例学会使用 静态代理、JDK动态代理、CGLIB动态代理
前言 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 代理 很多人至今都是看到 代理就懵, 静态代理.动态代理.JDK动态代理.CGL ...
- java基础5:工厂模式、单例模式、File文件类、递归、IO流、Properties配置文件、网络编程、利用IO流模拟注册登录功能、关于反射、JDK动态代理
1.工厂模式 23种java设计模式之一 1)提供抽象类(基类) 2)提供一些子类,完成方法重写 3)提供一个接口:完成具体子类的实例化对象的创建,不能直接new子类,构造函数私有化. 优点:具体的子 ...
- Cglib和jdk动态代理的区别
Cglib和jdk动态代理的区别 动态代理解决了方法之间的紧耦合, IOC解决了类与类之间的紧耦合! Cglib和jdk动态代理的区别? 1.Jdk动态代理:利用拦截器(必须实现InvocationH ...
- Jdk动态代理 底层源码分析
前言 java动态代理主要有2种,Jdk动态代理.Cglib动态代理,本文主要讲解Jdk动态代理的使用.运行机制.以及源码分析.当spring没有手动开启Cglib动态代理,即:<aop:asp ...
- Java设计模式(五)代理设计模式—静态代理—JDK动态代理—Cglib动态代理
文章目录 什么是代理模式 代理模式应用场景 代理的分类 静态代理 什么是静态代理 深入解析静态代理 小结 动态代理 什么是动态代理 JDK动态代理 原理和实现方式 代码实现 优缺点 Cglib动态代理 ...
- Cglib和jdk动态代理
前言:动态代理解决了方法之间的紧耦合,IOC解决了类与类之间的紧耦合. Cglib和jdk动态代理的区别? 1.Jdk动态代理:利用拦截器(必须实现InvocationHandler)加上反射机制生成 ...
最新文章
- SSD相关文章及博客
- Linux下Elasticsearch-2.4.0的安装与简单配置(单节点)Head插件安装(已测试)
- 如何合并多个(.txt或其他)文件到一个文件
- 手机控制linux工具下载,Scrcpy-Scrcpy(手机控制软件)下载 v1.16官方版--pc6下载站
- Linux perf tools
- Windows 禁止mysql 自动更新
- python的reduce()函数
- Python 异常处理
- MCMC算法大统一: Involutive MCMC
- 故障处理 软件 需求_如何做软件FMEA?
- Windows上配置iScsi发起程序,显示“CHAP密码不符合标准”
- QT控件被其他控件遮盖
- Hi3536调试记录,内核启动
- 2014 c语言程序设计形成性考核册,C语言程序设计形成性考核册参考答案
- STP分析--保险公司客户分类分析(采用SPSS进行分析)
- 5.1.6 守护进程daemon
- 浅谈Redis面试热点之工程架构篇[1]
- LabVIEW编程LabVIEW开发气体分析仪 MAPY 4.0例程与相关资料
- 网络安全事件层出不穷,是因为国家、组织还是个人?
- 物联网时代,物联感知技术将如何发展?
热门文章
- 极客大学架构师训练营 系统架构 消息队列 负载均衡 数据库备份 第10课 听课总结
- 算法: 删除链表中的数据Remove Linked List Elements
- 属兔的人今日运势-360星座网_明日运势属兔人(12.25)
- 二叉树的层序遍历 二叉树
- python中os模块作用
- 努力在html中适配图片尝试失败
- html5 sha1,JavaScript 实现的base64加密、md5加密、sha1加密及AES加密
- 【机器学习系列】GMM第二讲:高斯混合模型Learning问题,最大似然估计 or EM算法?
- 数据库基础(2)选择,投影,连接,除法运算
- Lagrangian乘子法 对偶问题 KKT条件 Slater条件 与凸优化