【深度思考】聊聊JDK动态代理原理
1. 示例
首先,定义一个接口:
public interface Staff {
void work();
}
然后,新增一个类并实现上面的接口:
public class Coder implements Staff {
@Override
public void work() {
System.out.println("认真写bug……");
}
}
假设现在有这么一个需求:在不改动以上类代码的前提下,对该方法增加一些前置操作或者后置操作。
接下来就来讲解下,如何使用JDK动态代理来实现这个需求。
首先,自定义一个调用处理器,实现java.lang.reflect.InvocationHandler接口并重写invoke方法:
public class AttendanceInvocationHandler implements InvocationHandler {
private final Object target;
public AttendanceInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("上班打卡……");
Object invoke = method.invoke(target, args);
System.out.println("下班打卡……");
return invoke;
}
}
重点看下Object invoke = method.invoke(target, args);,该行代码会执行真正的目标方法,在这前后,我们可以添加一些增强逻辑。
然后,新建个测试类,看下JDK动态代理如何使用:
public class JdkProxyTest {
public static void main(String[] args) {
Coder coder = new Coder();
AttendanceInvocationHandler h = new AttendanceInvocationHandler(coder);
// 创建代理对象
Object proxyInstance = Proxy.newProxyInstance(coder.getClass().getClassLoader(),
coder.getClass().getInterfaces(),
h);
Staff staff = (Staff) proxyInstance;
staff.work();
}
}
运行以上代码,效果如下图所示:

从运行结果可以看出,在目标方法的前后,执行了自定义的操作。
2. 原理
这里理解2个概念,目标对象和代理对象,
目标对象是真正要调用的对象,上面示例中的Coder类就是目标对象,
代理对象是JDK自动生成的对象,在代理对象内部会去调用目标对象的目标方法。
JDK动态代理的核心就是上面示例中的Proxy.newProxyInstance方法,方法签名如下图所示:

第1个参数传入的是目标对象的ClassLoader,第2个参数传入的是目标对象的接口信息,第3个参数传入的是自定义的InvocationHandler。
然后看下该方法的实现逻辑,先看第1处重点:

注释翻译过来是:查找或者生成指定的代理类。
该方法会生成代理类的字节码文件(也可能是从缓存中读取),核心逻辑在ProxyClassFactory类的apply方法中,
该方法中定义了生成的代理类的包名以及文件名:


因此默认情况下,自动生成的代理类名称是com.sun.proxy.$Proxy0。
该方法最后会生成代理类的字节码,默认情况下不会保存到文件系统,但可以通过参数指定保存到文件系统:


可以看出,保存不保存到文件系统,受saveGeneratedFiles的影响,其定义如下所示:
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));
所以可以通过指定sun.misc.ProxyGenerator.saveGeneratedFiles参数来让生成的代理类字节码文件保存到文件系统中。
然后看第2处重点:

先是获取构造函数,然后是生成代理类对象的实例。
3. 为什么必须要基于接口?
思考一个问题,为什么JDK动态代理必须要基于接口,带着这个问题,我们看下动态生成的代理类com.sun.proxy.$Proxy0长什么样子?
JVM参数里添加参数-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true,然后启动上面示例中的测试代码:


生成的代理类字节码文件保存在项目根目录下的com/sun/proxy目录下:

在IDEA中打开后,如下图所示:

在静态代码块中,对静态变量m0、m1、m2、m3进行了赋值,其中m3是要执行的目标方法。
在构造方法中,执行的是super(var1);,也就是父类Proxy的构造方法:

该方法是将我们自定义的InvocationHandler赋值给了父类的变量h。
而以下测试代码实际执行的是代理类$Proxy0里的work方法:
Staff staff = (Staff) proxyInstance;
staff.work();

代理类$Proxy0里的work方法实际执行的是自定义InvocationHandler里的invoke方法:

因此在执行目标方法前后,执行了自定义的前置操作和后置操作。
了解了这个调用过程,就理解了为什么JDK动态代理必须要基于接口,因为动态生成的代理类已经继承了类java.lang.reflect.Proxy,
而Java又是单继承的,如果想要继续对类进行扩展,只能通过实现接口的方式。
文章持续更新,欢迎关注微信公众号「申城异乡人」第一时间阅读!
【深度思考】聊聊JDK动态代理原理的更多相关文章
- 有点深度的聊聊JDK动态代理
在接触SpringAOP的时候,大家一定会被这神奇的功能所折服,想知道其中的奥秘,底层到底是如何实现的.于是,大家会通过搜索引擎,知道了一个陌生的名词:动态代理,慢慢的又知道了动态代理有多种实现方式, ...
- 代理模式(静态代理、JDK动态代理原理分析、CGLIB动态代理)
代理模式 代理模式是设计模式之一,为一个对象提供一个替身或者占位符以控制对这个对象的访问,它给目标对象提供一个代理对象,由代理对象控制对目标对象的访问. 那么为什么要使用代理模式呢? 1.隔离,客户端 ...
- Java设计模式之JDK动态代理原理
动态代理核心源码实现public Object getProxy() { //jdk 动态代理的使用方式 return Proxy.newProxyInstance( this.getClass(). ...
- 代理模式及jdk动态代理原理
代理模式 :为其它对象提供代理,以控制对这个对象的访问. 代理模式的特征:代理类(proxyClass)与委托类(realClass)有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转 ...
- jdk动态代理原理
http://www.cnblogs.com/MOBIN/p/5597215.html 请先查看这边博文 此文主要是在上篇博文的基础之上,宏观的理一下思路,因为之前本人看了上篇之后云里雾里(是本人 ...
- jdk动态代理使用及原理
jdk动态代理的使用 1.创建实现InvocationHandler接口的类,实现invoke(Object proxy, Method method, Object[] args)接口,其中invo ...
- JDK动态代理为什么必须要基于接口?
原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 前几天的时候,交流群里的小伙伴抛出了一个问题,为什么JDK的动态代理一定要基于接口实现呢? 好的安排,其实要想弄懂这个问题还是需要一些关于代理和 ...
- JDK动态代理与CGLib动态代理相关问题
导读: 1.JDK动态代理原理是什么?为什么不支持类的代理? 2.JDK动态代理实例 3.CGLib代理原理是什么? 4.CGLib代理实例 5.JDK动态代理与CGLib代理的区别是什么? 6.总结 ...
- Java,JDK动态代理的原理分析
1. 代理基本概念: 以下是代理概念的百度解释:代理(百度百科) 总之一句话:三个元素,数据--->代理对象--->真实对象:复杂一点的可以理解为五个元素:输入数据--->代理对象- ...
- JDK动态代理的实现原理
学习JDK动态代理,从源码层次来理解其实现原理参考:http://blog.csdn.net/jiankunking/article/details/52143504
随机推荐
- maven远程debug
1.修改tomcat服务器配置 打开tomcat/bin/catalina.sh 添加参数 CATALINA_OPTS="-Xdebug -Xrunjdwp:transport=dt_soc ...
- CatDCGAN项目复现与对抗网络初识
CatDCGAN项目复现与对抗网络初识 作者 CarpVexing 日期 100521 禁止转载 目录 CatDCGAN项目复现与对抗网络初识 引言 CatDCGAN项目基本信息 复现项目的准备工作 ...
- oneDNN
目录 oneDNN卷积思路 debug捆绑套路 jit_avx2_convolution_fwd_t::execute_forward( 整个文件oneDNN/src/cpu/x64/jit_avx2 ...
- Blog作业03
目录 前言 设计与分析 踩坑心得 改进建议 总结 前言 这三次作业的题目只有2道题,但是在题量减小的同时,这三次作业集的难度也相应的上去了,题目的质量很好,运用很广泛,也考验了很多的知识点.这三次的作 ...
- UIPath踩坑记一 对 COM 组件的调用返回了错误 HRESULT E_FAIL。UiPath.UiNodeClass.InjectAndRunJS
[ERROR] [UiPath.Studio] [1] 错误: System.Exception: 对 COM 组件的调用返回了错误 HRESULT E_FAIL. ---> System.Ex ...
- arp 基本功能
地址解析协议(英语:Address Resolution Protocol,缩写:ARP)是一个通过解析网络层地址来找寻数据链路层地址的网络传输协议,它在IPv4中极其重要.ARP最初在1982年的R ...
- nginx Redis 不能访问问题
开始以为 proxy_cookie_path /report/ /; 没有配置 配置后还是访问了,老的session 就过期 本地代理主程序,访问本地的 /report/ 可以 ...
- 实验九 团队作业6:团队项目编码与Alpha冲刺
项目 内容 课程班级博客链接 2018级卓越班 这个作业要求链接 实验九-团队作业6 团队名称 零基础619 团队成员分工描述 任务1:荣娟,鑫任务2:亚楠,桂婷任务3:亚楠,桂婷任务4:荣娟,鑫任务 ...
- [imx6ull][nand] uboot烧录固件
背景 在调试阶段使用nxp的mfg-tools烧录比较麻烦,故考虑使用uboot指令实现固件烧录 烧录方法 //烧写内核 nand erase 0x4000000 0x800000 tftp zIma ...
- 一文快速回顾 Java 操作数据库的方式-JDBC
前言 数据库的重要性不言而喻,不管是什么系统,什么应用软件,也不管它们是 Windows 上的应用程序,还是 Web 应用程序,存储(持久化)和查询(检索)数据都是核心的功能. 大家学习数据库时,比如 ...