什么是JDK动态代理
本篇内容主要讲解“什么是JDK动态代理”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“什么是JDK动态代理”吧!
林芝网站建设公司创新互联公司,林芝网站设计制作,有大型网站制作公司丰富经验。已为林芝上千多家提供企业网站建设服务。企业网站搭建\外贸营销网站建设要多少钱,请找那个售后服务好的林芝做网站的公司定做!
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
类,该类主要是为代理类进行缓存的。获取代理类时,会首先从缓存中获取,若没有会调用ProxyClassFactory
类进行创建,创建好后会进行缓存。
3.2.4 ProxyClassFactory类
ProxyClassFactory
是Proxy
类的一个静态内部类,该类用于生成代理类。下图是源码的部分内容:
代理类的名称就是在这里定义的,其前缀是
$Proxy
,后缀是一个数字。调用
ProxyGenerator.generateProxyClass
来生成指定的代理类。defineClass0
方法是一个native
方法,负责字节码加载的实现,并返回对应的Class
对象。
3.3 原理图
为了便于记录,将代理类的生成过程整理成了一张图。
到此,相信大家对“什么是JDK动态代理”有了更深的了解,不妨来实际操作一番吧!这里是创新互联网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
分享名称:什么是JDK动态代理
转载注明:http://cdiso.cn/article/jhcgho.html