JDK 动态代理实现原理
一、引言
Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象便能动态生成代理类。代理类会负责将所有方法的调用分派到委托对象上反射执行,在分派执行的过程中,开发人员还可以按需调整委托类对象及其功能。
本文首先从JDK动态代理的允许机制和特点出发,对其代码进行分析,以及推演动态生成类的内部实现。相关概念:代理类、委托类、调用处理器等。
二、相关类和接口
1、java.lang.reflect.Proxy:提供了一组静态方法为一组接口动态的生成代理类及其对象。
// 方法1:该方法用于获取指定代理类对象所关联的调用处理器
public static InvocationHandler getInvocationHandler(Object proxy) // 方法2:该方法用于获取指定类装载器和一组接口的动态代理类对象
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) // 方法3:该方法用于判断指定类对象是否是一个动态代理类
public static boolean isProxyClass(Class<?> cl) // 方法4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
2、java.lang.reflect.InvocationHandler:调用处理器接口,定义了一个invoke方法,用于集中处理【动态代理类对象】上的方法调用,通常在该方法中实现对委托类的代理访问。
3、java.lang.ClassLoader:类装载器,负责将类的字节码装载到Java虚拟机(JVM)中并为其定义类对象,然后该类才能被调用。Proxy静态方法生成【动态代理类】同样需要通过类装载器进行装载后才能使用,它与普通类的唯一区别就是其字节码是由JVM在运行时动态生成,而非预存在任何一个.class文件中。
三、实现步骤及其特点
1、实现步骤:一共四步,具体如下
- 1)创建调用处理器(通过实现InvocationHandler接口)
- 2)创建动态代理类(通过为Proxy类指定ClassLoader对象和一组interface)
- 3)获得动态代理类的构造函数(通过反射,其唯一参数类型是调用处理器接口类型)
- 4)创建动态代理类的实例(通过步骤3得到的构造函数,传入调用处理器对象作为参数)
// MyInvocationHandler实现了InvocationHandler接口,并能实现方法调用从代理类到委托类的分派转发
// 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用
InvocationHandler handler = new MyInvocationHandler(...); // 通过Proxy创建动态代理类
Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), new Class[] { Foo.class }); // 通过反射获得动态代理类的构造函数
Constructor constructor = proxyClass.getConstructor(new Class[] { InvocationHandler.class }); // 通过构造函数创建动态代理类实例
Foo f = (Foo) constructor.newInstance(new Object[] { handler });
实际使用过程更加简单,因为Proxy的静态方法newProxyInstance已经封装了步骤2至步骤4,所以简化后的过程如下
InvocationHandler handler = new MyInvocationHandler(...); // 通过Proxy直接创建动态代理类实例
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class[] { Foo.class }, handler);
2、代理类的特点
- 1)若所代理的接口中有非public接口则动态代理类将被定义在该接口所在包,否则将被定义在顶层包。目的是为了最大程度保证动态代理类不会因为包管理的问题而无法被成功定义并访问
- 2)该代理类具有final和public修饰符,意味着它可以被所有类访问,但是不能被再度继承。
- 3)类名格式是“$ProxyN”,其中N是一个逐一递增的阿拉伯数据,代表Proxy类第N次生成的动态代理类。值得注意的一点是,并不是每次调用Proxy的静态方法创建动态代理类都会是N值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,它会返回先前已经创建好的代理类对象,提高了创建效率
- 4)类继承关系:Proxy类是它的父类,且该类实现了其所代理的一组接口,这就是为什么它能够被安全的类型转换到其所代理的某接口的根本原因。
3、代理类实例的特点
- 1)每个实例都会关联一个调用处理器对象,可以通过Proxy提供的静态方法getInvocationHandler获取。
- 2)调用其代理接口中声明的方法时,最终都会由调用处理器的invoke方法执行,包括hashCode、equals、toString。
- 3)当代理的一组接口有重复声明的方法且该方法被调用时,代理类总是从排在最前面的接口获取方法对象并分派给调用处理器,因为在代理类内部无法区分其当前的被引用类型。
4、被代理接口的特点
- 1)不能有重复的接口,以避免动态代理类代码生成时的编译错误
- 2)这些接口对类装载器必须可见,否则会导致类定义失败
- 3)被代理的所有非public接口必须在同一个包中,否则也会导致类定义失败,原因见【代理类的特点1】
- 4)接口的数目不能超过(2的16次方-1),这是JVM设定的限制
5、异常处理的特点
- 1)理论上调用处理器可以抛出任何类型的异常,因为所有异常都继承于Throwable接口,但实际上往往受限制,除非父接口中的方法支持抛Throwable异常
- 2)如果在invoke方法中产生了不支持的异常,它将抛出UndeclaredThrowableException异常,这个异常时RuntimeException类型,所以不会引起编译错误。通过该异常的getCause方法可以获得那个不受支持的异常对象,以便与错误诊断。
四、Proxy类实现原理
1、Proxy的重要静态变量
// 映射表:用于维护类装载器对象到其对应的代理类缓存
private static Map<ClassLoader, Map<List<String>, Object>> loaderToCache = new WeakHashMap<>(); // 标记:用于标记一个动态代理类正在被创建中
private static Object pendingGenerationMarker = new Object(); // 同步表:记录已经被创建的动态代理类类型,主要用于方法isProxyClass 进行相关的判断
private static Map<Class<?>, Void> proxyClasses = Collections.synchronizedMap(new WeakHashMap<Class<?>, Void>()); // 关联的调用处理器引用
protected InvocationHandler h;
2、Proxy构造方法
// 由于 Proxy 内部从不直接调用构造函数,所以 private 类型意味着禁止任何调用
private Proxy() {} // 由于 Proxy 内部从不直接调用构造函数,所以 protected 意味着只有子类可以调用
protected Proxy(InvocationHandler h) {
doNewInstanceCheck();
this.h = h;
}
3、Proxy静态方法newProxyInstance,详见jar包
4、getProxyClass方法详解,四个步骤
- 1)对接口进行安全检查,包括检查a.是否对类装载器可见(通过Class.forName方法判断),b.确保是interface而不是class类型,c.接口是否重复,检查通过后得到一个包含所有接口名称的字符串数组。
- 2)从loaderToCache映射表获取以类装载器为key所对应的缓存表(接口列表为key,动态代理类对象为value),如果不存在则创建一个并更新到loaderToCache。当代理类正在被创建时它会临时保存(接口列表,pendingGenerationMarker)。标记pendingGenerationMarker的作用是通知后续的同类请求(接口数组相同且组内接口顺序也相同)代理类正在被创建,请保持等待直至创建完成。
- 3)动态创建代理类的对象。首先确定代理类所在的包,检查是否所有非public的接口都在同一个包,否则抛异常终止代理类生成;之后开始生成代理类的类名,格式如前所述“$ProxyN”;最后动态生成代理类。
// 动态地生成代理类的字节码数组
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);
try {
// 动态地定义新生成的代理类
proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
} // 把生成的代理类记录进 proxyClasses 表
proxyClasses.put(proxyClass, null); - 4)最后根据结果更新缓存表,如果成功则将代理类更新进缓存表,否则清除缓存表对应的key,再唤醒所有正在等待的线程。
五、代理类中方法调用的分派转发推演实现
// 假设需代理接口 Simulator
public interface Simulator {
short simulate(int arg1, long arg2, String arg3) throws ExceptionA, ExceptionB;
} // 假设代理类为 SimulatorProxy, 其类声明将如下
final public class SimulatorProxy implements Simulator { // 调用处理器对象的引用
protected InvocationHandler handler; // 以调用处理器为参数的构造函数
public SimulatorProxy(InvocationHandler handler){
this.handler = handler;
} // 实现接口方法 simulate
public short simulate(int arg1, long arg2, String arg3)
throws ExceptionA, ExceptionB { // 第一步是获取 simulate 方法的 Method 对象
java.lang.reflect.Method method = null;
try{
method = Simulator.class.getMethod(
"simulate",
new Class[] {int.class, long.class, String.class} );
} catch(Exception e) {
// 异常处理 1(略)
} // 第二步是调用 handler 的 invoke 方法分派转发方法调用
Object r = null;
try {
r = handler.invoke(this,
method,
// 对于原始类型参数需要进行装箱操作
new Object[] {new Integer(arg1), new Long(arg2), arg3});
} catch(Throwable e) {
// 异常处理 2(略)
}
// 第三步是返回结果(返回类型是原始类型则需要进行拆箱操作)
return ((Short)r).shortValue();
}
}
从以上推演中可以得出一个通用的结构化流程:1.从代理接口获取被调用的方法对象,2.分派方法到调用处理器执行,3.返回结果
六、参考资料
1、http://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html
JDK 动态代理实现原理的更多相关文章
- 解析JDK动态代理实现原理
JDK动态代理使用实例 代理模式的类图如上.关于静态代理的示例网上有很多,在这里就不讲了. 因为本篇讲述要点是JDK动态代理的实现原理,直接从JDK动态代理实例开始. 首先是Subject接口类. p ...
- JDK动态代理实现原理--转载
之前虽然会用JDK的动态代理,但是有些问题却一直没有搞明白.比如说:InvocationHandler的invoke方法是由谁来调用的,代理对象是怎么生成的,直到前几个星期才把这些问题全部搞明白了. ...
- jdk 动态代理的原理
一.代理设计模式 代理设计模式是Java常用的设计模式之一. 特点: 01.委托类和代理类有共同的接口或者父类: 02.代理类负责为委托类处理消息,并将消息转发给委托类: 03.委托类和代理类对象通常 ...
- Java,JDK动态代理的原理分析
1. 代理基本概念: 以下是代理概念的百度解释:代理(百度百科) 总之一句话:三个元素,数据--->代理对象--->真实对象:复杂一点的可以理解为五个元素:输入数据--->代理对象- ...
- JDK动态代理实现原理
之前虽然会用JDK的动态代理,但是有些问题却一直没有搞明白.比如说:InvocationHandler的invoke方法是由谁来调用的,代理对象是怎么生成的.直到看了他的文章才彻底明白,附网址:htt ...
- 代理模式(静态代理、JDK动态代理原理分析、CGLIB动态代理)
代理模式 代理模式是设计模式之一,为一个对象提供一个替身或者占位符以控制对这个对象的访问,它给目标对象提供一个代理对象,由代理对象控制对目标对象的访问. 那么为什么要使用代理模式呢? 1.隔离,客户端 ...
- JDK动态代理
一.基本概念 1.什么是代理? 在阐述JDK动态代理之前,我们很有必要先来弄明白代理的概念.代理这个词本身并不是计算机专用术语,它是生活中一个常用的概念.这里引用维基百科上的一句话对代理进行定义: A ...
- 【原创】JDK动态代理,此次之后,永生难忘。
动态代理,这个词在Java的世界里面经常被提起,尤其是对于部分(这里强调“部分”二字,因为有做了一两年就成大神的,实力强的令人发指,这类人无疑是非常懂动态代理这点小伎俩的)做了一两年新人来说,总是摸不 ...
- JDK动态代理深入理解分析并手写简易JDK动态代理(下)
原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-05/27.html 作者:夜月归途 出处:http://www.guitu ...
随机推荐
- php 笔试面试 总结
一次小小的笔试面试经历,虽然是一些简单的问题,但是自己在这儿总结一下,也查一些资料,得出一些较好的答案,也能帮助自己成长. 1.自己熟悉的http状态码及其意义 其实这个题答案随处可见.这儿也还是记录 ...
- java.util.concurrent.CountDownLatch
闭锁是一种同步工具类,可以延迟线程的进度直到闭锁到达终止状态. 闭锁的作用相当于一扇门,在闭锁到达结束状态之前,这扇门一直是关闭的,任何线程都不能通过这扇门,当闭锁到达结束状态时,这扇门会打开并允许所 ...
- 剑指Offer12 数组奇数调整至偶数前
/************************************************************************* > File Name: 12_Reorde ...
- uva 12544 无向图最小环
思路:这题的N有500,直接floyd肯定超时. 我的做法是每次枚举一个点,求出包含这个点的最小环. 对所有最小环取最小值.求包含某个点的最小环我用的是启发式搜索,先以该点求一次spfa,然后dfs解 ...
- 转: adroid音视延迟 10ms的原因与解答
https://github.com/hehonghui/android-tech-frontier/blob/master/issue-9/Android%2010ms%E9%97%AE%E9%A2 ...
- canvas基础2--绘制图形
栅格 绘制矩形 不同于SVG,HTML中的元素canvas只支持一种原生的图形绘制:矩形.所有其他的图形的绘制都至少需要生成一条路径.不过,我们拥有众多路径生成的方法让复杂图形的绘制成为了可能. 首先 ...
- JNA参数传递问题,Java数组
版权声明:本文为博主原创文章,未经博主允许不得转载. 本文主要讲述使用JNA模拟结构体并将结构体数组作为参数传递给对应的方法. C语言结构体定义如下: typedef struct Rect { in ...
- 设置input(radio,checkbox)和lable对齐的问题
在做页面的时候几次遇到label和前面的小图标无法对齐的情况,后来发现解决方法不过是 label { display:inline-block; vertical-align:top; line-he ...
- Miniui updateRow更改列字段值
当UPC等于upccode时,更改列Scanned+1 //先grid.findRow找到UPC等于upccode的行对象 var row = grid.findRow(function (row) ...
- list转换成DataTable
list转换成DataTable类如下: public static DataTable ToDataTable<T>(this IList<T> datas) { DataT ...