linuxsir首页 LinuxSir.Org | Linux、BSD、Solaris、Unix | 开源传万世,因有我参与欢迎您!
网站首页 | 设为首页 | 加入收藏
您所在的位置:主页 > Linux基础建设 >

深入理解动态代理源码

时间:2019-11-20  来源:未知  作者:admin666

前言:  早期学习了动态代理在实际开发中的使用场景和使用方法,我们也知道了最经典的mybatis的mapper就是采用动态代理来实现的,那么动态代理的背后是怎样的原理?为什么能实现动态代理?为什么动态代理只可以代理接口,而无法代理普通类?为什么动态代理需要传入类的classLoder和接口?带着这些疑问,我们来开启本期的主题:探究动态代理的内部原理。

本篇博客的目录

一:动态代理的基本使用方法

二:动态代理的内部运行过程

三:几个相关的问题

四:总结

一:动态代理的基本使用方法

 1.1:简单例子

首先我们来模拟一个简单的动态代理的过程:某歌手去参加一个晚会,需要唱歌,他在演奏的过程中需要别人来报幕:演奏开始、演奏结束,每个歌手都遵循这样的过程,在歌手进行表演的过程,穿插着主持人的开场白和结语,我们来用代码模拟这个场景:

1.2:代理接口

首先我们来定义一个singer接口表示我们将要代理的接口:

 

public interface Singer {
    /**
    * 表演
    * @param soonName
    */
    public void perform(String soonName);
}

 

1.3:接口的具体实现类

 

public class Jay implements Singer {
 
    public void perform(String soonName) {
        System.out.println("接下来我为大家唱一首"+soonName);
    }
}

 

1.4:辅助类,用来模拟注册人的前后台词

 

public class Presenter  {

    public void before(){
        System.out.println("请开始你的表演!");
    }

    public void after(){
        System.out.println("表演结束,大家鼓掌!");
    }
}

 

1.5:具体的代理类

  这里用proxy.newProxyInstance来创建一个代理类,传入原始类的类加载器和接口与接口InvocationHandler,同时插入Presenter类的before与after方法,用于前置和后置处理

 

public class SingerProxy {

  private Presenter presenter;

  public SingerProxy(Presenter presenter){
      this.presenter = presenter;
  }
    /**
    * 获取代理对象
    * @return
    */
    public Singer getProxy(){

        final Singer jay = new Jay();
        Singer singerProxy = (Singer)Proxy.newProxyInstance(jay.getClass().getClassLoader(), jay.getClass().getInterfaces(), new InvocationHandler() {」
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                presenter.before();
                method.invoke(jay, args);
                presenter.after();
                return null;
            }
        });
        return singerProxy;
    }
}

 

1.6:测试类

 

public class Test {
    public static void main(String[] args) {
        SingerProxy singerProxy = new SingerProxy(new Presenter());
        Singer proxy = singerProxy.getProxy();
        proxy.perform("《夜曲》");
    }
}

 

输出:

 二:动态代理的内部探究

从上面的例子可以看出我们首先生成了一个代理类,然后用代理类来调用原始接口的方法,就可以实现我们的预设的逻辑,在原始接口的前后(或者出现异常的时候)插入我们想要的逻辑,那么究竟是为什么呢?

2.1:找到生成的代理类

我们如果需要打开生成的类,首先需要在测试类中添加这行代码,设置系统属性来保存生成的代理类的class文件:

System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

2.2:singerProxy类

通过动态代理生成的代理类名为:$Proxy0.class然后通过intelj idea反编译之后源代码是这样的,这里主要看到有4个方法,method的m1\m2\m3\m0;分别由反射获取的equals()、toString()、perform()、hashcode()方法,同时代理类继承了proxy并且实现了原始Singer接口,重写了perform()方法,所以这就解释了为什么代理类可以调用perform()方法,在perform方法中,又调用了父类中的InvoationHander的invoke方法,并且传入原始接口中的方法,而invoke方法在我们在创建代理类的时候重写过,所以就会按照我们自定义的逻辑调用invoke方法,按照顺序执行

 

import Java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import main.learn.proxy.Singer;

public final class $Proxy0 extends Proxy implements Singer {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(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);
        }
    }

    public final void perform(String var1) throws  {
        try {
            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);
        }
    }

    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("main.learn.proxy.Singer").getMethod("perform", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

 

 2.3:生成代理类的过程

上面我们弄明白了,在代理类中会自动继承原始接口类并且会调用InvocationHandler将接口类中的方法传入进去,那么这个类是如何生成的呢?这就要翻生成代理类的源码了:

 

 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
        * Look up or generate the designated proxy class.
        */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
        * Invoke its constructor with the designated invocation handler.
        */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

 

上面的代码由下来解释:

2.3.1:安全管理器检查与代理权限检查

2.3.2:判断接口的长度是否大于65535,大于不可往下进行。然后从WeakCache缓存中获取代理类,如果找不到则通过proxyClassFactory生成代理类

2.3.2.1:生成代理类的class过程如下:

1??验证传入的interface是否可被传入的classloader装载

2??验证传入的是否是一个接口,如果不是一个接口,直接抛出IllegalArgumentException异常

3??判断传入的是否重复,这里是通过一个IdentityHashMap往里面put接口的class类,如果返回值不为null表示这个接口已经注册过了(如果第一次put会返回null,按照传统的做法是先get是否为null,如果不为null再put,这行代码很妙省去了这个步骤),IdentityHashMap也是map的一种,不过它与我们普通的HashMap最大的不同在于它不会通过equals方法和hashCode方法去判断key是否重复,而是通过==运算符

4??拼接代理类的名字固定为:com.sun.proxy.$Proxy+原子自增序号,为了防止并发调用,在生成代理类名字的时候,采用了AtomicLong的getAndIncrement方法去原子递增代理类的序列号,这个方法是原子的,所以不会产生并发问题。这里也就是我们为什么看到最后的代理类是$Proxy0的原因(生成的代理类的序号是从0开始的)

5??调用ProxyGenerator.generateProxyClass方法来生成代理的class类(过程较为复杂、通过一些jvm指令去生成字节码,包括遍历方法类型、返回值、参数类型等)

6??通过defineClass将上一步产生的class字节码生成class文件,该方法也是一个native方法,需要传入类的classloader来进行装载生成的类字节码进而生成class

2.3.3: 通过反射获取构造器constractor创建一个反射实例,这个过程进行了强制构造器的private私有化反射

三:几个相关的问题

 3.1:为什么动态代理需要传入classLoader?

  主要原因有以下几个:

1??需要校验传入的接口是否可被当前的类加载器加载,假如无法加载,证明这个接口与类加载器不是同一个,按照双亲委派模型,那么类加载层次就被破坏了

2??需要类加载器去根据生成的类的字节码去通过defineClass方法生成类的class文件,也就是说没有类加载的话是无法生成代理类的

3.2:为什么动态代理需要传入接口和只能代理接口?

 需要接口去通过ProxyGenerator类来生成代理类的字节码,在生成的过程中,需要遍历接口中的方法,包括方法签名、参数类型、返回类型从而生成新的代理类,而代理类也需要继承原始接口的方法,所以接口必须要传

3.3:如果同一个接口创建多次代理会怎么办?

在获取代理对象的时候首先会从缓存(WeakCache)里面取,如果取不到才会通过代理工厂去创建,所以如果创建多个代理类的话,最终只会产生一个代理类

四:总结

    本篇博客通过一个动态代理的实际例子来分析了具体创建动态代理的过程,分析了动态代理的内部运行原理,以及分析了生成的代理类的源码,动态代理在我们的开发过程中可谓是非常常见,比如最典型的mybatis的mapper代理原理、spring的aop实现原理,进行前置增强、后置增强等就是借助了动态代理。理解了动态代理能帮助我们进一步理解一些源码,或许在以后的某些特定场景我们也可以采用动态代理来造出合适的轮子。

linux
友情链接
  • Mozilla发布Firefox 67.0.4,修复沙箱逃逸漏洞
  • 蚂蚁金服正式成为CNCF云原生计算基金会黄金会员
  • Firefox 68将采用Microsoft BITS安装更新
  • OpenSSH增加对存储在RAM中的私钥的保护
  • 谷歌想实现自己的curl,为什么?
  • Raspberry Pi 4发布:更快的CPU、更大的内存
  • Firefox的UA将移除CPU架构信息
  • Ubuntu放弃支持32位应用程序实属乌龙,Steam会否重回Ubuntu怀抱
  • Qt 5.13稳定版发布:引入glTF 2.0、改进Wayland以及支持Lottie动
  • 红帽企业Linux 7现已内置Redis 5最新版
  • Slack进入微软内部禁用服务清单,GitHub也在其列?
  • 安全的全新编程语言V发布首个可用版本
  • Windows Terminal已上架,快尝鲜
  • 阿里巴巴微服务开源生态报告No.1
  • 面世两年,Google地球将支持所有基于Chromium的浏览器
  • 推进企业容器化持续创新,Rancher ECIC千人盛典完美收官
  • CentOS 8.0最新构建状态公布,或于数周后发布
  • Debian移植RISC
  • 微软拆分操作系统的计划初现雏形
  • Oracle发布基于VS Code的开发者工具,轻松使用Oracle数据库
  • Ubuntu 19.10停止支持32位的x86架构
  • 微软为Windows Terminal推出全新logo
  • 联想ThinkPad P系列笔记本预装Ubuntu系统
  • 微软发布适用于Win7/8的Microsoft Edge预览版
  • 启智平台发布联邦学习开源数据协作项目OpenI纵横
  • 经过六个多月的延迟,微软终于推出Hyper
  • ZFS On Linux 0.8.1 发布,Python可移植性工作
  • DragonFly BSD 5.6.0 发布,HAMMER2状态良好
  • Linux Kernel 5.2
  • CentOS 8.0 看起来还需要几周的时间
  • 百度网盘Linux版正式发布
  • PCIe 6.0宣布:带宽翻倍 狂飙至256GB/s
  • PHP 7.4 Alpha 发布,FFI扩展,预加载Opcache以获得更好的性能
  • Canonical将在未来的Ubuntu版本中放弃对32位架构的支持
  • Scala 2.13 发布,改进的编译器性能
  • 微软的GitHub收购了Pull Panda,并且使所有订阅完全免费
  • Windows Subsystem for Linux 2 (WSL 2)现在适用于Windows 10用
  • Debian 10 “Buster”的RISC
  • MariaDB宣布发布MariaDB Enterprise Server 10.4
  • DXVK 1.2.2 发布,带来微小的CPU开销优化
  • DragonFlyBSD 5.6 RC1 发布,VM优化,默认为HAMMER2
  • PrimeNG 8.0.0 发布,支持Angular 8,FocusTrap等
  • GIMP 2.10.12 发布,一些有用的改进
  • 清华大学Anaconda 镜像服务即将恢复
  • Debian GNU/Linux 10 “Buster” 操作系统将于2019年7月6日发布
  • 时时彩论坛
  • 五星体育斯诺克
  • 北单比分直播
  • 河北11选5走势图
  • 福建体彩36选7开奖结果
  • 九龙图库下载