微信搜寻:码农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,并且在调用代理实例的办法时,会被转到InvocationHandlerinvoke办法上。

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办法中的三个参数下面曾经介绍过,通过调用methodinvoke办法来实现办法的调用。

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

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办法获取代理实例

具体实现步骤如下:

  1. 依据类加载器和接口数组获取代理类的Class对象
  2. 过Class对象的结构器创立一个实例(代理类的实例)
  3. 将代理实例强转成指标接口Foo(因为代理类实现了指标接口,所以能够强转)。
  4. 最初应用代理进行办法调用。
@Testpublic 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.$Proxy4method - public abstract java.lang.String io.github.gozhuyinglong.proxy.Foo.ping(java.lang.String)args - [杨过]pingpong

通过输入后果能够看出:

  • 代理类的名称是以$Proxy结尾的。
  • 办法实例为代理类调用的办法。
  • 参数为代理类调用办法时传的参数。

2.4 形式二:通过newProxyInstance办法获取代理实例

通过这种办法是最简略的,也是举荐应用的,通过该办法能够间接获取代理对象。

注:其实该办法后盾实现理论与下面应用getProxyClass办法的过程一样。

@Testpublic 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表达式进行简化的实现,如下代码:

@Testpublic 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类,并实现了被代理接口Foopublic 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类为咱们提供的两个静态方法开始getProxyClassnewProxyInstance。下面曾经介绍了,这两个办法是用来创立代理类及其实例的,上面来看源码。

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类

ProxyClassFactoryProxy类的一个动态外部类,该类用于生成代理类。下图是源码的局部内容:

  • 代理类的名称就是在这里定义的,其前缀是$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
CSDNhttps://blog.csdn.net/gozhuyinglong
掘进https://juejin.cn/user/1239904849494856
Githubhttps://github.com/gozhuyinglong
Giteehttps://gitee.com/gozhuyinglong