基于JDK动态代理原理解析

 更新时间:2023年05月05日 08:59:45   作者:wen-pan  
这篇文章主要介绍了基于JDK动态代理原理解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

Java技术迷

1、回顾一下JDK动态代理的核心参数

如果我们要为target类创建一个【JDK动态代理对象】,那么我们必须要传入如下三个核心参数

  • 加载target类的类加载器
  • target类实现的接口
  • InvocationHandler

为什么必须要这三个参数呢?之前使用动态代理的时候都是直接按接口要求传这三个参数,但从来没想过为什么?下面仔细去探究一下

2、实现一个简单的动态代理

【JDK动态代理】的核心其实是借助【Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)】方法,去创建的动态代理对象,我们这里也使用这个方法去创建一个简单的【动态代理对象】以便于理解他的核心原理。

①、定义接口Subject

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Subject {
    /**
     * 接口方法
     */
    void doSomething();
    /**
     * sayHello
     *
     * @param name name
     * @return string
     */
    String sayHello(String name);
}

②、定义接口的实现类RealSubject

实现Subject接口,并实现接口相关方法

1
2
3
4
5
6
7
8
9
10
11
public class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("RealSubject do something");
    }
    @Override
    public String sayHello(String name) {
        System.out.println("RealSubject sayHello");
        return "hello-" + name;
    }
}

③、定义代理对象创建工厂

  • 定义一个工厂类,该工厂类用于为target对象生产代理对象
  • 该工厂类借助Proxy.newProxyInstance来为目标对象创建代理对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class JdkDynamicProxyFactory {
    /**
     * 创建target类的代理对象
     * 注意:当调用代理对象中的方法时,其实就是调用的InvocationHandler里面的invoke方法,然后在invoke方法里调用目标对象对应的方法
     *
     * @param <T> 泛型
     * @return 代理对象
     */
    public static <T> T getProxy(Object target) {
        // 创建代理实例,分别传入:【加载target类的类加载器、target类实现的接口、InvocationHandler】
        Object proxyInstance = Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("执行目标方法前");
                        // 执行目标方法
                        Object result = method.invoke(target, args);
                        System.out.println("执行目标方法后");
                        // 返回目标方法的执行结果
                        return result;
                    }
                });
        // 返回代理对象
        return (T) proxyInstance;
    }
}

④、创建测试类,为target创建代理对象

该测试类会将内存中的动态代理对象保存到磁盘上,以便于我们后续分析生成的动态代理类的具体结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Client {
    public static void main(String[] args) {
        // 保存生成的代理类的字节码文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 目标对象
        RealSubject target = new RealSubject();
        // 使用JDK动态代理为【target对象】创建代理对象
        Subject proxy = JdkDynamicProxyFactory.getProxy(target);
        // 调用代理对象的方法
        proxy.doSomething();
        System.out.println("=====================华丽的分割线=====================");
        proxy.sayHello("wenpan");
    }
}

3、分析生成的动态代理类的结构

在上面一步中我们使用

1
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

将动态生成的代理保存到了磁盘上,下面我们就具体看看生成的代理类长什么样

  • 可以看到代理类【继承了Proxy类】,并且【实现了目标接口Subject】,覆写了接口中的【每一个方法】
  • 这也说明了为什么JDK代理需要实现接口,因为Java是单继承的,既然代理类继承了Proxy类那么就无法再继承其他类了
  • 在代理类中将我们的【目标接口Subject】的【所有方法(包括object父类的方法)】都以【静态属性的形式】保存了起来(主要是为了方便后面的【反射调用】)
  • 在调用动态代理对象的某个方法时(比如:调用doSomething方法),实质上是调用的【Proxy类】的【h属性】的invoke方法
  • 所以我们要重点去看看这个【Proxy.h】到底是个什么鬼?其实他就是创建代理对象是我们传入的【InvocationHandler】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// 1、代理类首先继承了Proxy类(这也说明了为什么JDK代理需要实现接口,因为Java是单继承的),并且实现了目标接口Subject
public final class $Proxy0 extends Proxy implements Subject {
    // 2、可以看到,在代理类中将我们的【目标接口Subject】的【所有方法(包括object父类的方法)】都以【静态属性的形式】保存了起来
    private static Method m1;
    private static Method m3;
    private static Method m4;
    private static Method m2;
    private static Method m0;
    // 以静态代码块的形式为属性赋值
    static {
          try {
              m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
              m3 = Class.forName("com.stone.design.mode.proxy.jdk.Subject").getMethod("doSomething");
              m4 = Class.forName("com.stone.design.mode.proxy.jdk.Subject").getMethod("sayHello", Class.forName("java.lang.String"));
              m2 = Class.forName("java.lang.Object").getMethod("toString");
              m0 = Class.forName("java.lang.Object").getMethod("hashCode");
          } catch (NoSuchMethodException var2) {
              throw new NoSuchMethodError(var2.getMessage());
          } catch (ClassNotFoundException var3) {
              throw new NoClassDefFoundError(var3.getMessage());
          }
      }
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    // 3、object父类的equals方法
    public final boolean equals(Object var1) throws  {
        try {
            // 这里的supper是指的Proxy类,调用【Proxy类】的【h属性】的invoke方法执行
            // 重点:【注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看
            // 这里也就体现了创建代理对象时为什么需要传入【InvocationHandler】,以及为什么调用代理对象的方法时都是执行的invoke方法
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
    // 4、目标接口的方法
    public final void doSomething() throws  {
        try {
            // 调用了【Proxy.h属性】的invoke方法
            // 注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
    // 5、目标接口的方法
    public final String sayHello(String var1) throws  {
        try {
            // 调用了【Proxy.h属性】的invoke方法
            // 注意这里的super.h】其实就是我们创建代理对象是传入的【InvocationHandler】,不信往后面看
            return (String)super.h.invoke(this, m4, 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);
        }
    }
    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);
        }
    }
}

4、Proxy.h是什么鬼

在上面的第三点中我们看到,执行代理对象的方法的时候其实质是调用的【super.h.invoke方法】也就是【Proxy.h.invoke方法】,那么我们仔细看看【Proxy.h】到底是什么鬼

①、Proxy.newProxyInstance是如何创建代理对象的

下面的源代码我做了一些删减,只留下了最核心的部分

  • 通过下面代码我们就明确了使用【newProxyInstance】方法创建代理对象时所做的几件事情
  • 首先通过【目标接口】 + 【类加载器】创建一个Proxy类的【Class对象】
  • 然后通过这个【Class对象】获取到他的有参构造器,并且传入我们自定义的【InvocationHandler】作为构造函数参数,并且通过反射的方式调用有参构造器创建一个【Proxy对象】
  • 在【Proxy的有参构造器】中,会将传入的【InvocationHandler】保存到 【h属性】上(方便后面的supper.h.invoke调用)
  • 代理对象创建完毕
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Proxy implements java.io.Serializable {
    // h属性,保存我们传递进来的InvocationHandler
    protected InvocationHandler h;
    // 【有参构造器】注意这里的参数
    protected Proxy(InvocationHandler h) {
      Objects.requireNonNull(h);
      this.h = h;
    }
    // 生成代理对象的方法
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
        throws IllegalArgumentException{
        // 1、InvocationHandler强制不允许为空
        Objects.requireNonNull(h);
        // 获取到目标接口
        final Class<?>[] intfs = interfaces.clone();
        /*
         * Look up or generate the designated proxy class.
         * 2、获取到代理类的Class对象(也就是Proxy)
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        /*
         * Invoke its constructor with the designated invocation handler.
         * 通过反射执行 cl 的有参构造,也就是下面这个,可以看到通过反射执行Proxy有参构造,
         * 将InvocationHandler赋值到了h属性上
         */
        try {
            // 3、获取到有参构造器
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            // 4、通过构造器来创建一个代理对象并返回,这里传入的参数h 就是我们的【InvocationHandler】
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            // 省略....
        }
    }
}

5、问题总结

现在再来看上面抛出的三个问题!

为什么创建代理对象时需要传入如下三个参数:

  • 加载target类的类加载器
  • target类实现的接口
  • InvocationHandler

①、为什么要传入类加载器

因为动态代理类也是类,我们普通的类是从【磁盘上的.class文件(也可以是其他地方,比如网络上)】里加载而来,而动态代理类则是在【运行过程中动态生成的类】。

那么既然是类那么他就一定要【被类加载器加载后】才能被我们的【Java虚拟机】识别。

所以我们会传入【加载target类的类加载器】,用该类加载器来加载【动态生成的代理类】

②、为什么要传入target类实现的接口

为啥要传入【target类实现的接口】呢?直接【继承目标类】不行吗?肯定不行

  • 从上面的【动态生成的代理类的结构】来看,代理类继承了Proxy类,由于【Java是单继承】的,所以无法再通过继承的方式来继承【目标类】了。
  • 所以动态代理类需要【实现目标接口】,来重写接口的方法

③、为什么要传入InvocationHandler

  • 从【动态生成的代理类的结构】可以看出,我们传入的InvocationHandler最终会被作为一个属性保存到Proxy对象的【h属性】上
  • 并且【动态代理对象】会覆写【目标接口的所有方法】,在方法中会使用 supper.h.invoke的方式调用InvocationHandler的invoke方法,所以我们需要传入InvocationHandler
  • 动态代理的每个方法调用都会先走InvocationHandler.invoke()方法

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

蓄力AI

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

原文链接:https://blog.csdn.net/Hellowenpan/article/details/123482681

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!

相关文章

  • 使用Mybatis Plus整合多数据源和读写分离的详细过程

    使用Mybatis Plus整合多数据源和读写分离的详细过程

    这篇文章主要介绍了Mybatis Plus整合多数据源和读写分离的详细过程,mybatisplus可以整合阿里的分布式事务组件seata,本文通过示例代码给大家介绍的非常详细,需要的朋友参考下吧
    2021-09-09
  • java多线程导入excel的方法

    java多线程导入excel的方法

    最近项目写了poi导入excel数据到数据库,想把学到的知识用于实践,于是使用多线程方式导入excel,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • 使用RedisAtomicLong优化性能问题

    使用RedisAtomicLong优化性能问题

    这篇文章主要介绍了使用RedisAtomicLong优化性能问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • Java同步函数代码详解

    Java同步函数代码详解

    这篇文章主要介绍了Java线程中的同步函数的相关内容,涉及了实例代码,需要的朋友,可以参考下。
    2017-10-10
  • Java如何自定义异常打印非堆栈信息详解

    Java如何自定义异常打印非堆栈信息详解

    这篇文章主要给大家介绍了关于Java如何自定义异常打印非堆栈信息的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。
    2018-04-04
  • 你所不知道的Spring自动注入详解

    你所不知道的Spring自动注入详解

    这篇文章主要给大家介绍了关于你所不知道的Spring自动注入的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • Java使用HttpClient实现文件下载

    Java使用HttpClient实现文件下载

    这篇文章主要为大家详细介绍了Java使用HttpClient实现文件下载,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-08-08
  • Messges Queue消息队列详解

    Messges Queue消息队列详解

    这篇文章主要介绍了Messges Queue消息队列详解,消息队列一般简称为 MQ,是指利用高效可靠的消息传递机制进行与平台无关的数据交流,并基于数据通信来进行分布式系统的集成,是在消息的传输过程中保存消息的容器,需要的朋友可以参考下
    2023-07-07
  • 浅析java中Integer传参方式的问题

    浅析java中Integer传参方式的问题

    以下是对java中Integer传参方式的问题进行了详细的介绍,需要的朋友可以过来参考下
    2013-09-09
  • Spring实现控制反转和依赖注入的示例详解

    Spring实现控制反转和依赖注入的示例详解

    这篇文章主要为大家详细介绍IoC(控制反转)和DI(依赖注入)的概念,以及如何在Spring框架中实现它们,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-08-08

最新评论