JDK动态代理原理

JDK动态代理是指:代理类实例在程序运行时,由JVM根据反射机制动态的生成。也就是说代理类不是用户自己定义的,而是由JVM生成的。

由于其原理是通过Java反射机制实现的,所以在学习前,要对反射机制有一定的了解。传送门:Java反射机制:跟着代码学反射

下面是本篇讲述内容:

 chatgpt免费体验:http://www.chat136.com

chatgpt学习:http://me.chat136.com

1. JDK动态代理的核心类

JDK动态代理有两大核心类,它们都在Java的反射包下(java.lang.reflect),分别为InvocationHandler接口和Proxy类。

1.1 InvocationHandler接口

代理实例的调用处理器需要实现InvocationHandler接口,并且每个代理实例都有一个关联的调用处理器。当一个方法在代理实例上被调用时,这个方法调用将被编码并分派到其调用处理器的invoke方法上。

也就是说,我们创建的每一个代理实例都要有一个关联的InvocationHandler,并且在调用代理实例的方法时,会被转到InvocationHandler的invoke方法上。

publicObject 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对象,再通过调用构造函数创建代理实例。

该方法有两个参数:

loader:为类加载器。intefaces:为接口的Class对象数组。返回值为动态代理类的Class对象。

1.2.2 newProxyInstance

用于创建一个代理实例。

该方法有三个参数:

loader:为类加载器。interfaces:为接口的Class对象数组。h:指定的调用处理器。返回值为指定接口的代理类的实例。

1.3 小结

Proxy类主要用来获取动态代理对象,InvocationHandler接口主要用于方法调用的约束与增强。

2. 获取代理实例的代码示例

上一章中已经介绍了获取代理实例的两个静态方法,现在通过代码示例来演示具体实现。

2.1 创建目标接口及其实现类

JDK动态代理是基于接口的,我们创建一个接口及其实现类。

Foo接口:

Foo接口的实现类RealFoo:

2.2 创建一个InvocationHandler

创建一个InvocationHandler接口的实现类MyInvocationHandler。该类的构造方法参数为要代理的目标对象。

invoke方法中的三个参数上面已经介绍过,通过调用method的invoke方法来完成方法的调用。

这里一时看不懂没关系,后面源码解析章节会进行剖析。

2.3 方式一:通过getProxyClass方法获取代理实例

具体实现步骤如下:

根据类加载器和接口数组获取代理类的Class对象过Class对象的构造器创建一个实例(代理类的实例)将代理实例强转成目标接口Foo(因为代理类实现了目标接口,所以可以强转)。最后使用代理进行方法调用。

输出结果:

通过输出结果可以看出:

代理类的名称是以$Proxy开头的。方法实例为代理类调用的方法。参数为代理类调用方法时传的参数。2.4 方式二:通过newProxyInstance方法获取代理实例

通过这种方法是最简单的,也是推荐使用的,通过该方法可以直接获取代理对象。

注:其实该方法后台实现实际与上面使用getProxyClass方法的过程一样。

2.5 通过Lambda表达式简化实现

其实InvocationHander接口也不用创建一个实现类,可以使用Lambad表达式进行简化的实现,如下代码:

3. 源码解析

3.1 代理类$Proxy是什么样子

JVM为我们自动生成的代理类到底是什么样子的呢?下面我们先来生成一下,再来看里面的构造。

3.1.1 生成$Proxy的.class文件

JVM默认不创建该.class文件,需要增加一个启动参数:

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

在IDEA中点击【Edit Configurations...】,打开 Run/Debug Configurations 配置框。

将上面启动参数加到【VMoptions】中,点击【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,下面是内容:

通过该文件可以看出:

代理类继承了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,若对你有帮助,欢迎给个,感谢~~

blog-demos/java-source-analysis/src/main/java/io/github/gozhuyinglong/proxy at main · gozhuyinglong/blog-demos · GitHub

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值