• 详解Java动态代理机制


         之前介绍的反射和注解都是Java中的动态特性,还有即将介绍的动态代理也是Java中的一个动态特性。这些动态特性使得我们的程序很灵活。动态代理是面向AOP编程的基础。通过动态代理,我们可以在运行时动态创建一个类,实现某些接口中的方法,目前为止该特性已被广泛应用于各种框架和类库中,例如:Spring,Hibernate,MyBatis等。理解动态代理是理解框架底层的基础。
         主要内容如下:

    • 理解代理是何意
    • Java SDK实现动态代理
    • 第三方库cglib实现动态代理

    一、代理的概念
         单从字面上理解,代理就是指原对象的委托人,它不是原对象但是却有原对象的权限。Java中的代理意思类似,就是指通过代理来操作原对象的方法和属性,而原对象不直接出现。这样做有几点好处:

    • 节省创建原对象的高开销,创建一个代理并不会立马创建一个实际原对象,而是保存一个原对象的地址,按需加载
    • 执行权限检查,保护原对象

    这里写图片描述

    实际上代理堵在了原对象的前面,在代理的内部往往还是调用了原对象的方法,只是它还做了其他的一些操作。下面看第一种实现动态代理的方式。

    二、Java SDK实现动态代理
         实现动态代理主要有如下几个步骤:

    • 实现 InvocationHandler接口,完成自定义调用处理器
    • 通过Proxy的getProxyClass方法获取对应的代理类
    • 利用反射技术获取该代理类的constructor构造器
    • 利用constructor构造代理实例对象

    在一步步解析源码之前,我们先通过一个完整的实例了解下,整个程序的一步步逻辑走向。

    //定义了一个调用处理器
    public class MyInvotion implements InvocationHandler {
    
        private Object realObj;
    
        public MyInvotion(Object obj){
            this.realObj =  obj;
        }
        
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
    	    //通过代理执行原对象的方法
            return method.invoke(realObj,args);
        }
    }
    
    //定义一个接口
    public interface MyInterface {
        
        public void sayHello();
    }
    
    //该接口的一个实现类,该类就是我们的原对象
    public class ClassA implements MyInterface {
    
        public void sayHello(){
            System.out.println("hello walker");
        }
    }
    
    //main 函数
    public static void main(String[] args){
            ClassA a = new ClassA();
            MyInvotion myInvotion = new MyInvotion(a);
            Class myProxy = Proxy.getProxyClass(ClassA.class.getClassLoader(), new Class[]{MyInterface.class});
            Constructor constructor =myProxy.getConstructor(new Class[]{InvocationHandler.class});
            MyInterface m = (MyInterface)constructor.newInstance(myInvotion);
            m.sayHello();
        }
    
    输出结果:hello walker
    

    简单说下整体的运行过程,首先我们创建ClassA 实例并将它传入自定义的调用处理器MyInvotion,在MyInvotion中用realObj接受该参数代表原对象。接着调用Proxy的getProxyClass方法,将ClassA 的类加载器和ClassA 的实现的接口集合传入,该方法内部会实现所有接口返回该类的代理类,然后我们利用反射获取代理类的构造器并创建实例。

    以上便是整个程序运行的大致流程,接下来我们从源代码的角度看看具体是如何实现的。首先我们看InvocationHandler接口,这是我们的调用处理器,在代理类中访问的所有的方法都会被转发到这执行,具体的等我们看了代理类源码及理解了。该接口中唯一的方法是:

    public Object invoke(Object proxy, Method method, Object[] args)
    
    • 参数Proxy表示动态生成的代理类的对象,基本没啥用
    • 参数method表示当前正在被调用的方法
    • 数组args指定了该方法的参数集合

    我们上例中对该接口的实现情况,定义了一个realObj用于保存原对象的引用。重写的invoke方法中调用了原对象realObj的method方法,具体谁来调用该方法以及传入的参数是什么,在看完代理类源码即可知晓。

    接下来我们看看最核心的内容,如何动态创建代理类。这是getProxyClass方法的源码:

        public static Class<?> getProxyClass(ClassLoader loader,
                                             Class<?>... interfaces)
            throws IllegalArgumentException
        {
            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }
    
            return getProxyClass0(loader, intfs);
        }
    

    首先获取了该类实现的所有的接口的集合,然后判断创建该代理是否具有安全性问题,检查接口类对象是否对类装载器可见等。然后调用另外一个getProxyClass0方法,我们跟进去:

        private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
            if (interfaces.length > 65535) {
                throw new IllegalArgumentException("interface limit exceeded");
            }
    
            // If the proxy class defined by the given loader implementing
            // the given interfaces exists, this will simply return the cached copy;
            // otherwise, it will create the proxy class via the ProxyClassFactory
            return proxyClassCache.get(loader, interfaces);
        }
    

    判断如果该类的接口超过65535(想必还没有那么牛的类),抛出异常。在我们的Proxy类中有个属性proxyClassCache,这是一个WeakCache类型的静态变量。它指示了我们的类加载器和代理类之间的映射。所以proxyClassCache的get方法用于根据类加载器来获取Proxy类,如果已经存在则直接从cache中返回,如果没有则创建一个映射并更新cache表。具体创建一个Proxy类并存入cache表中的代码限于能力,未能参透。

    至此我们就获取到了该ClassA类对应的代理类型,接着我们通过该类的getConstructor方法获取该代理类的构造器,并传入InvocationHandler.class作为参数,至于为何要传入该类型作为参数,等会看代理类源码变一目了然了。

    最后newInstance创建该代理类的实例,实现对ClassA对象的代理。

    可能看完上述的介绍,你还会有点晕。下面我们通过查看动态生成的代理类的源码来加深理解。上述getProxyClass方法会动态创建一个代理类并返回他的Class类型,这个代理类一般被命名为$ProxyN,这个N是递增的用于标记不同的代理类。我们可以利用反编译工具反编译该class:

    final class $Proxy0 extends Proxy implements MyInterface {
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m0;
    
        public $Proxy0(InvocationHandler paramInvocationHandler) {
            super(paramInvocationHandler);
        }
    
        public final boolean equals(Object paramObject) {
            return ((Boolean) this.h.invoke(this, m1, 
                    new Object[] { paramObject })).booleanValue();
        }
    
        public final void sayHello() {
            this.h.invoke(this, m3, null);
        }
    
        public final String toString() {
            return (String) this.h.invoke(this, m2, null);
        }
    
        public final int hashCode() {
            return ((Integer) this.h.invoke(this, m0, null)).intValue();
        }
    
        static {
            m1 = Class.forName("java.lang.Object").getMethod("equals",
                    new Class[] { Class.forName("java.lang.Object") });
            m3 = Class.forName("laoma.demo.proxy.SimpleJDKDynamicProxyDemo$IService")
                    .getMethod("sayHello",new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        }
    }
    

    这就是上述的ClassA的动态代理类,我们看到该类的构造方法传入参数InvocationHandler类型,并调用了父类Proxy的构造方法保存了这个InvocationHandler实例,这也解释了我们为什么在获取构造器的时候需要指定参数类型为InvocationHandler,就是因为动态代理类只有一个构造器并且参数类型为InvocationHandler。

    接着我们看其中的方法,貌似只有一个sayHello是我们知道的,别的方法哪来的?我们说过在动态创建代理类的时候,会实现原对象的所有接口。所以sayHello方法是实现的MyInterface。而其余的四个方法是代理类由于比较常用,被默认添加到其中。而这些方法的内部都是调用的this.h.invoke这个方法,this.h就是保存在父类Proxy中的InvocationHandler实例(我们用构造器向其中保存的),调用了这个类的invoke方法,在我们自定义的InvocationHandler实例中重写了invoke方法,我们写的比较简单,直接执行传入的method。

    也就是我们调用代理类的任何一个方法都会转发到该InvocationHandler实例中的involve中,因为该实例中保存有我们的原对象,所以我们可以选择直接调取原对象中的方法作为回调。

    以上便是有关Java SDK中动态代理的相关内容,稍微总结下,首先我们通过实现InvocationHandler自定义一个调用处理类,该类中会保存我们的原对象,并提供一个invoke方法供代理类使用。然后我们通过getProxyClass方法动态创建代理类,最后用反射获取代理类的实例对象。

    需要注意的是:以上我们使用的四步创建代理实例时最根本的,其实Proxy中提供一个方法可以封装2到4步的操作。上述代码也可以这么写:

    ClassA a = new ClassA();
    MyInterface aProxy = (MyInterface)Proxy.newProxyInstance(ClassA.class.getClassLoader(),new Class<?>[]{MyInterface.class},new MyInvotion(a));
    aProxy.sayHello();
    

    我们打开该方法的内部源码,其实走的还是我们上述的过程,它就是做了封装。

    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
    {
    
    	Objects.requireNonNull(h);
    		
    		//获取所有接口
            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }
    		
    		//创建动态代理类
            Class<?> cl = getProxyClass0(loader, intfs);
    
            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});
    	.............
    	..............
    }
    

    二、第三方库cglib实现动态代理
         使用动态代理,我们编写通用的代码逻辑,即仅实现一个InvocationHandler实例完成对多个类型的代理。但是我们从动态生成的代理类的源码可以看到,所有的代理类都继承自Proxy这个类,这就导致我们这种方式不能代理类,只能代理接口。因为java中是单继承的。也就是说,给我们一个类型,我们只能动态实现该类所有的接口类型,但是该类继承的别的类我们在代理类中是不能使用的,因为它没有被代理类继承。下面看个例子:

    public class ClassB {
        public void welcome(){
            System.out.println("welcom walker");
        }
    }
    public interface MyInterface {
    
        public void sayHello();
    }
    
    //需要被代理的原类型,继承了ClassB和接口MyInterface 
    public class ClassA extends ClassB implements MyInterface {
    
        public void sayHello(){
            System.out.println("hello walker");
        }
    }
    
    //InvocationHandler 实例
    public class MyInvotion implements InvocationHandler {
    
    private Object realObj;
    
    public MyInvotion(Object obj){
         this.realObj =  obj;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
            return method.invoke(realObj,args);
        }
    }
    

    我们反编译该代理类,和上述的源码是一样的,此处不再重复贴出。我们能从中看出来的是,我们的代理只会实现原类型中所有的接口,至于原类型所继承的类,在生成Proxy代理类的时候会丢弃,因为所有的代理类必须继承Proxy类,这就导致原类型的父类中的方法 在代理类中丢失。这是该种方式的一大弊端。下面我们看看另一种方式实现动态代理,该种方式完美解决了这种不足。

    限于篇幅,我们下篇介绍cglib实现动态代理机制的内容,本篇暂时结束,总结的不好,望海涵。

  • 相关阅读:
    redis特性,使用场景
    docker 界面话管理工具Portainer
    解决streamsets jdbc全量模式数据重复问题
    clickhouse 行列转换
    clickHouse 常用命令
    mysqldump导出数据
    oracle 迁移到clickhouse 45亿条数据
    clickHouse 导入/删除/更新数据
    clickhouse Mysql 数据类型对比
    maven 的使用
  • 原文地址:https://www.cnblogs.com/yangming1996/p/6820180.html
Copyright © 2020-2023  润新知