共计 7387 个字符,预计需要花费 19 分钟才能阅读完成。
微信搜寻:码农 StayUp
主页地址:https://gozhuyinglong.github.io
源码分享:https://github.com/gozhuyinglong/blog-demos
JDK 动静代理是指:代理类实例在程序运行时,由 JVM 依据反射机制动静的生成。也就是说代理类不是用户本人定义的,而是由 JVM 生成的。
因为其原理是通过 Java 反射机制实现的,所以在学习前,要对反射机制有肯定的理解。传送门:Java 反射机制:跟着代码学反射
上面是本篇讲述内容:
1. JDK 动静代理的外围类
JDK 动静代理有两大外围类,它们都在 Java 的反射包下(java.lang.reflect
),别离为 InvocationHandler
接口和 Proxy
类。
1.1 InvocationHandler 接口
代理实例的调用处理器须要实现
InvocationHandler
接口,并且每个代理实例都有一个关联的调用处理器。当一个办法在代理实例上被调用时,这个办法调用将被编码并分派到其调用处理器的invoke
办法上。
也就是说,咱们创立的每一个代理实例都要有一个关联的 InvocationHandler
,并且在调用代理实例的办法时,会被转到InvocationHandler
的invoke
办法上。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
该 invoke
办法的作用是:解决代理实例上的办法调用并返回后果。
其有三个参数,别离为:
- proxy:是调用该办法的代理实例。
- method:是在代理实例上调用的接口办法对应的
Method
实例。 - args:一个
Object
数组,是在代理实例上的办法调用中传递的参数值。如果接口办法为无参,则该值为 null。
其返回值为:调用代理实例上的办法的返回值。
1.2 Proxy 类
Proxy
类提供了创立动静代理类及其实例的静态方法,该类也是动静代理类的超类。
代理类具备以下属性:
- 代理类的名称以“$Proxy”结尾,前面跟着一个数字序号。
- 代理类继承了
Proxy
类。 - 代理类实现了创立时指定的接口(JDK 动静代理是面向接口的)。
- 每个代理类都有一个公共构造函数,它承受一个参数,即接口
InvocationHandler
的实现,用于设置代理实例的调用处理器。
Proxy
提供了两个静态方法,用于获取代理对象。
1.2.1 getProxyClass
用于获取代理类的 Class
对象,再通过调用构造函数创立代理实例。
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
该办法有两个参数:
- loader:为类加载器。
- intefaces:为接口的
Class
对象数组。
返回值为动静代理类的 Class
对象。
1.2.2 newProxyInstance
用于创立一个代理实例。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
该办法有三个参数:
- loader:为类加载器。
- interfaces:为接口的
Class
对象数组。 - h:指定的调用处理器。
返回值为指定接口的代理类的实例。
1.3 小结
Proxy
类次要用来获取动静代理对象,InvocationHandler
接口次要用于办法调用的束缚与加强。
2. 获取代理实例的代码示例
上一章中曾经介绍了获取代理实例的两个静态方法,当初通过代码示例来演示具体实现。
2.1 创立指标接口及其实现类
JDK 动静代理是基于接口的,咱们创立一个接口及其实现类。
Foo 接口:
public interface Foo {String ping(String name);
}
Foo 接口的实现类 RealFoo:
public class RealFoo implements Foo {
@Override
public String ping(String name) {System.out.println("ping");
return "pong";
}
}
2.2 创立一个 InvocationHandler
创立一个 InvocationHandler
接口的实现类 MyInvocationHandler。该类的构造方法参数为要代理的指标对象。
invoke
办法中的三个参数下面曾经介绍过,通过调用 method
的invoke
办法来实现办法的调用。
这里一时看不懂没关系,前面源码解析章节会进行分析。
public class MyInvocationHandler implements InvocationHandler {
// 指标对象
private final Object target;
public MyInvocationHandler(Object target) {this.target = target;}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("proxy -" + proxy.getClass());
System.out.println("method -" + method);
System.out.println("args -" + Arrays.toString(args));
return method.invoke(target, args);
}
}
2.3 形式一:通过 getProxyClass 办法获取代理实例
具体实现步骤如下:
- 依据类加载器和接口数组获取代理类的 Class 对象
- 过 Class 对象的结构器创立一个实例(代理类的实例)
- 将代理实例强转成指标接口 Foo(因为代理类实现了指标接口,所以能够强转)。
- 最初应用代理进行办法调用。
@Test
public void test1() throws Exception {Foo foo = new RealFoo();
// 依据类加载器和接口数组获取代理类的 Class 对象
Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
// 通过 Class 对象的结构器创立一个实例(代理类的实例)Foo fooProxy = (Foo) proxyClass.getConstructor(InvocationHandler.class)
.newInstance(new MyInvocationHandler(foo));
// 调用 ping 办法,并输入返回值
String value = fooProxy.ping("杨过");
System.out.println(value);
}
输入后果:
proxy - class com.sun.proxy.$Proxy4
method - public abstract java.lang.String io.github.gozhuyinglong.proxy.Foo.ping(java.lang.String)
args - [杨过]
ping
pong
通过输入后果能够看出:
- 代理类的名称是以
$Proxy
结尾的。 - 办法实例为代理类调用的办法。
- 参数为代理类调用办法时传的参数。
2.4 形式二:通过 newProxyInstance 办法获取代理实例
通过这种办法是最简略的,也是举荐应用的,通过该办法能够间接获取代理对象。
注:其实该办法后盾实现理论与下面应用 getProxyClass 办法的过程一样。
@Test
public void test2() {Foo foo = new RealFoo();
// 通过类加载器、接口数组和调用处理器,创立代理类的实例
Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[]{Foo.class},
new MyInvocationHandler(foo));
String value = fooProxy.ping("小龙女");
System.out.println(value);
}
2.5 通过 Lambda 表达式简化实现
其实 InvocationHander
接口也不必创立一个实现类,能够应用 Lambad 表达式进行简化的实现,如下代码:
@Test
public void test3() {Foo foo = new RealFoo();
Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[]{Foo.class},
(proxy, method, args) -> method.invoke(foo, args));
String value = fooProxy.ping("雕兄");
System.out.println(value);
}
3. 源码解析
3.1 代理类 $Proxy 是什么样子
JVM 为咱们主动生成的代理类到底是什么样子的呢?上面咱们先来生成一下,再来看外面的结构。
3.1.1 生成 $Proxy 的.class 文件
JVM 默认不创立该.class 文件,须要减少一个启动参数:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
在 IDEA 中点击【Edit Configurations…】,关上 Run/Debug Configurations 配置框。
将下面启动参数加到【VM options】中,点击【OK】即可。
再次运行代码,会在我的项目中的【com.sun.proxy】目录中找到这个.class 文件,我这里是“$Proxy4.class”
3.1.2 为什么加上这段启动参数就能生成 $Proxy 的字节码文件
在 Proxy
类中有个 ProxyClassFactory
动态外部类,该类次要作用就是生成动态代理的。
其中有一段代码 ProxyGenerator.generateProxyClass
用来生成代理类的.class 文件。
其中变量 saveGeneratedFiles
便是援用了此启动参数的值。将该启动参数配置为 true
会生成.class 文件。
3.1.3 这个代理类 $Proxy 到底是什么样子呢
神秘的面纱行将揭发,后面很多未解之迷在这里能够找到答案!
关上这个 $Proxy
文件,我这里生成的是$Proxy4
,上面是内容:
// 该类为 final 类,其继承了 Proxy 类,并实现了被代理接口 Foo
public final class $Proxy4 extends Proxy implements Foo {
// 这 4 个 Method 实例,代表了本类实现的 4 个办法
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
// 动态代码块依据反射获取这 4 个办法的 Method 实例
static {
try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("io.github.gozhuyinglong.proxy.Foo").getMethod("ping");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());
}
}
// 一个公开的构造函数,参数为指定的 InvocationHandler
public $Proxy4(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 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);
}
}
// Foo 接口的实现办法,最终调用了 InvocationHandler 中的 invoke 办法
public final String ping(String var1) throws {
try {return (String)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);
}
}
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);
}
}
}
通过该文件能够看出:
- 代理类继承了
Proxy
类,其次要目标是为了传递InvocationHandler
。 - 代理类实现了被代理的接口 Foo,这也是为什么代理类能够间接强转成接口的起因。
- 有一个公开的构造函数,参数为指定的
InvocationHandler
,并将参数传递到父类Proxy
中。 - 每一个实现的办法,都会调用
InvocationHandler
中的invoke
办法,并将代理类自身、Method 实例、入参三个参数进行传递。这也是为什么调用代理类中的办法时,总会分派到InvocationHandler
中的invoke
办法的起因。
3.2 代理类是如何创立的
咱们从 Proxy
类为咱们提供的两个静态方法开始 getProxyClass
和newProxyInstance
。下面曾经介绍了,这两个办法是用来创立代理类及其实例的,上面来看源码。
3.2.1 getProxyClass 和 newProxyInstance 办法
通过下面源码能够看出,这两个办法最终都会调用 getProxyClass0
办法来生成代理类的 Class
对象。只不过 newProxyInstance
办法为咱们创立好了代理实例,而 getProxyClass
办法须要咱们本人创立代理实例。
3.2.2 getProxyClass0 办法
上面来看这个对立的入口:getProxyClass0
从源码和注解能够看出:
- 代理接口的最多不能超过 65535 个
- 会先从缓存中获取代理类,则没有再通过
ProxyClassFactory
创立代理类。(代理类会被缓存一段时间。)
3.2.3 WeakCache 类
这里简略介绍一下 WeakCache<K, P, V>
类,该类次要是为代理类进行缓存的。获取代理类时,会首先从缓存中获取,若没有会调用 ProxyClassFactory
类进行创立,创立好后会进行缓存。
3.2.4 ProxyClassFactory 类
ProxyClassFactory
是 Proxy
类的一个动态外部类,该类用于生成代理类。下图是源码的局部内容:
- 代理类的名称就是在这里定义的,其前缀是
$Proxy
,后缀是一个数字。 - 调用
ProxyGenerator.generateProxyClass
来生成指定的代理类。 defineClass0
办法是一个native
办法,负责字节码加载的实现,并返回对应的Class
对象。
3.3 原理图
为了便于记录,将代理类的生成过程整顿成了一张图。
源码分享
残缺代码请拜访我的 Github,若对你有帮忙,欢送给个⭐,感激~~????????????
https://github.com/gozhuyinglong/blog-demos/tree/main/java-source-analysis/src/main/java/io/github/gozhuyinglong/proxy
举荐浏览
- Java 反射机制:跟着代码学反射
对于作者
我的项目 | 内容 |
---|---|
公众号 | 码农 StayUp(ID:AcmenStayUp) |
主页 | https://gozhuyinglong.github.io |
CSDN | https://blog.csdn.net/gozhuyinglong |
掘进 | https://juejin.cn/user/1239904849494856 |
Github | https://github.com/gozhuyinglong |
Gitee | https://gitee.com/gozhuyinglong |