• 动态代理的两种方式


    动态代理是反射的一个非常重要的应用场景。动态代理常被用于一些 Java 框架中。例如 Spring 的 AOP ,Dubbo 的 SPI 接口,就是基于 Java 动态代理实现的。

    动态代理的方式有两种:

    • JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
    • CGLIB动态代理:利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

    JDK动态代理和CGLIB字节码生成的区别?

    • JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
    • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。

    因为是继承,所以该类或方法最好不要声明成final 。

    一、静态代理

    了解动态代理之前,我们先来看看静态代理。

    静态代理其实就是指设计模式中的代理模式。

    代理模式为其他对象提供一种代理以控制对这个对象的访问。

    Subject 定义了 RealSubject 和 Proxy 的公共接口,这样就在任何使用 RealSubject 的地方都可以使用 Proxy 。

    abstract class Subject {
        public abstract void Request();
    }

    RealSubject 定义 Proxy 所代表的真实实体。

    class RealSubject extends Subject {
        @Override
        public void Request() {
            System.out.println("真实的请求");
        }
    }

    Proxy 保存一个引用使得代理可以访问实体,并提供一个与 Subject 的接口相同的接口,这样代理就可以用来替代实体。

    class Proxy extends Subject {
        private RealSubject real;
    
        @Override
        public void Request() {
            if (null == real) {
                real = new RealSubject();
            }
            real.Request();
        }
    }

    静态代理模式固然在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;并且由于 Proxy 和 RealSubject 的功能本质上是相同的,Proxy 只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。

    二、JDK动态代理

     为了解决静态代理的问题,就有了创建动态代理的想法:

     在运行状态中,需要代理的地方,根据 Subject 和 RealSubject,动态地创建一个 Proxy,用完之后,就会销毁,这样就可以避免了 Proxy 角色的 class 在系统中冗杂的问题了。

     

     Java 动态代理基于经典代理模式,引入了一个 InvocationHandler,InvocationHandler 负责统一管理所有的方法调用。

     动态代理步骤:

    • 获取 RealSubject 上的所有接口列表;
    • 确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX
    • 根据需要实现的接口信息,在代码中动态创建 该 Proxy 类的字节码;
    • 将对应的字节码转换为对应的 class 对象;
    • 创建 InvocationHandler 实例 handler,用来处理 Proxy 所有方法调用;
    • Proxy 的 class 对象 以创建的 handler 对象为参数,实例化一个 proxy 对象。

    从上面可以看出,JDK 动态代理的实现是基于实现接口的方式,使得 Proxy 和 RealSubject 具有相同的功能。 

    在 Java 的动态代理机制中,有两个重要的类(接口),一个是 InvocationHandler 接口、另一个则是 Proxy 类,这一个类和一个接口是实现我们动态代理所必须用到的。

    1. InvocationHandler 接口

    InvocationHandler 接口定义:

    public interface InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    }

    每一个动态代理类都必须要实现 InvocationHandler 这个接口,并且每个代理类的实例都关联到了一个 Handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由 InvocationHandler 这个接口的 invoke 方法来进行调用。

    我们来看看 InvocationHandler 这个接口的唯一一个方法 invoke 方法:

    Object invoke(Object proxy, Method method, Object[] args) throws Throwable

    参数说明:

    • proxy - 代理的真实对象。
    • method - 所要调用真实对象的某个方法的 Method 对象
    • args - 所要调用真实对象某个方法时接受的参数
    2. Proxy 类

    Proxy 这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

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

    这个方法的作用就是得到一个动态的代理对象。

    参数说明:

    • loader - 一个 ClassLoader 对象,定义了由哪个 ClassLoader 对象来对生成的代理对象进行加载。
    • interfaces - 一个 Interface 对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
    • h - 一个 InvocationHandler 对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个 InvocationHandler 对象上
    3. 动态代理示例

    首先我们定义了一个 Subject 类型的接口,为其声明了两个方法:

    public interface Subject {
        void hello(String str);
        String bye();
    }

    接着,定义了一个类来实现这个接口,这个类就是我们的真实对象,RealSubject 类:

    public class RealSubject implements Subject {
    
        @Override
        public void hello(String str) {
            System.out.println("Hello  " + str);
        }
    
        @Override
        public String bye() {
            System.out.println("Goodbye");
            return "Over";
        }
    }

    下一步,我们就要定义一个动态代理类了,前面说个,每一个动态代理类都必须要实现 InvocationHandler 这个接口,因此我们这个动态代理类也不例外:

    public class InvocationHandlerDemo implements InvocationHandler {
        // 这个就是我们要代理的真实对象
        private Object subject;
    
        // 构造方法,给我们要代理的真实对象赋初值
        public InvocationHandlerDemo(Object subject) {
            this.subject = subject;
        }
    
        @Override
        public Object invoke(Object object, Method method, Object[] args)
            throws Throwable {
            // 在代理真实对象前我们可以添加一些自己的操作
            System.out.println("Before method");
    
            System.out.println("Call Method: " + method);
    
            // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
            Object obj = method.invoke(subject, args);
    
            // 在代理真实对象后我们也可以添加一些自己的操作
            System.out.println("After method");
            System.out.println();
    
            return obj;
        }
    }

    最后,来看看我们的 Client 类:

    public class Client {
        public static void main(String[] args) {
            // 我们要代理的真实对象
            Subject realSubject = new RealSubject();
    
            // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
            InvocationHandler handler = new InvocationHandlerDemo(realSubject);
    
            /*
             * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
             * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
             * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
             * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
             */
            Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                    .getClass().getInterfaces(), handler);
    
            System.out.println(subject.getClass().getName());
            subject.hello("World");
            String result = subject.bye();
            System.out.println("Result is: " + result);
        }
    }

    控制台输出:

    com.sun.proxy.$Proxy0
    Before method
    Call Method: public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String)
    Hello  World
    After method
    
    Before method
    Call Method: public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye()
    Goodbye
    After method
    
    Result is: Over

    我们首先来看看 com.sun.proxy.$Proxy0 这东西,我们看到,这个东西是由 System.out.println(subject.getClass().getName()); 这条语句打印出来的,那么为什么我们返回的这个代理对象的类名是这样的呢?

    Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                    .getClass().getInterfaces(), handler);

    可能我以为返回的这个代理对象会是 Subject 类型的对象,或者是 InvocationHandler 的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为 Subject 类型的对象?

    原因就是:在 newProxyInstance 这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是 Subject 类型,所以就可以将其转化为 Subject 类型了。

    同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在 jvm 运行时动态生成的一个对象,它并不是我们的 InvocationHandler 类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy 为中,最后一个数字表示对象的标号

    接着我们来看看这两句:

    subject.hello("World");
    String result = subject.bye();

    这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的 invoke 方法去执行,而我们的这个 handler 对象又接受了一个 RealSubject 类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的 invoke 方法去执行。

    我们看到,在真正通过代理对象来调用真实对象的方法的时候,我们可以在该方法前后添加自己的一些操作,同时我们看到我们的这个 method 对象是这样的:

    public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String)
    public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye()

    正好就是我们的 Subject 接口中的两个方法,这也就证明了当我通过代理对象来调用方法的时候,起实际就是委托由其关联到的 handler 对象的 invoke 方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。

    三、CGLIB动态代理

    1. 添加依赖
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.1</version>
    </dependency>

    目标类(一个公开方法,另外一个用final修饰):

    public class Dog{
        
        final public void run(String name) {
            System.out.println("狗"+name+"----run");
        }
        
        public void eat() {
            System.out.println("狗----eat");
        }
    }

    方法拦截器

    public class MyMethodInterceptor implements MethodInterceptor {
    
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("这里是对目标类进行增强!!!");
            //注意这里的方法调用,不是用反射哦!!!
            Object object = proxy.invokeSuper(obj, args);
            return object;
        }  
    }

    测试

    public class CgLibProxy {
        public static void main(String[] args) {
            //在指定目录下生成动态代理类,我们可以反编译看一下里面到底是一些什么东西
            //System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\java\java_workspace");
            
            //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
            Enhancer enhancer = new Enhancer();
            //设置目标类的字节码文件
            enhancer.setSuperclass(Dog.class);
            //设置回调函数
            enhancer.setCallback(new MyMethodInterceptor());
            
            //这里的creat方法就是正式创建代理类
            Dog proxyDog = (Dog)enhancer.create();
            //调用代理类的eat方法
            proxyDog.eat();       
        }
    }

    四、小结

    参考

    https://www.cnblogs.com/jingmoxukong/p/12049112.html

  • 相关阅读:
    html5基础的常用的技巧
    安全验证之使用摘要认证(digest authentication)
    自制AutoMapper实现DTO到持久层Entity的转换
    javascript ajax 脚本跨域调用全解析
    Padrino 生成器指南
    jQuery设计思想
    MS SQL SERVER 2008 使用OBJECT_ID判断临时表是否存在
    C#基础原理拾遗——引用类型的值传递和引用传递
    Java的23种设计模式
    使用实时文件夹显示联系人信息
  • 原文地址:https://www.cnblogs.com/myitnews/p/12940371.html
Copyright © 2020-2023  润新知