zoukankan      html  css  js  c++  java
  • JDK动态代理、CGLIB动态代理详解

    Spring的AOP其就是通过动态代理的机制实现的,所以理解动态代理尤其重要。

    动态代理比静态代理的好处:

    1.一个动态代理类可以实现多个业务接口。静态代理的一个代理类只能对一个业务接口的实现类进行包装,如果有多个业务接口的话就要定义很多实现类和代理类才行。

    2.提供统一的方法前后处理。如果代理类对业务方法的预处理、调用后操作都是一样的(比如:调用前输出提示、调用后自动关闭连接),则多个静态代理类就会有很多重复代码。这时我们可以定义这样一个代理类,它能代理所有实现类的方法调用:根据传进来的业务实现类和方法名进行具体调用。——那就是动态代理。

    一.JDK动态代理(针对接口)

    一个类(BookManage)实现了一个接口(IBookManage),这个类就是我们需要代理的真实对象。

    proxy = Proxy.newProxyInstance()创建动态代理对象,然后proxy.真实对象的方法。这时就会进入动态代理对象的invoke()方法里。

    动态代理类:实现InvocationHandler接口,并实现里面的invoke()方法。invoke()方法里传入的是真实对象(BookManage),通过代理对象来调用方法时,是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。

    代码实现:

    1.1 接口类

    package jdkproxy;
    
    /**
     * @author admin
     *接口类
     */
    public interface IBookManage {
        //添加图书
        public void addBook();
    }

    1.2 接口实现类

    package jdkproxy;
    
    /**
     * @author admin
     *接口实现类,真实对象
     */
    public class BookManage implements IBookManage {
    
        @Override
        public void addBook() {
            System.out.println("addBook...");
        }
    
    }

    1.3 动态代理类

    package jdkproxy;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    /**
     * @author 梦里南柯
       *  动态代理类
     */
    public class DynamicProxy implements InvocationHandler {
     private Object target;// 这其实业务实现类对象,用来调用具体的业务方法
     /**
      * 绑定业务对象并返回一个代理类
      * 
      * @param target
      * @return
      */
     public Object bind(Object target) {
      this.target = target; // 接收业务实现类对象参数
      /*
       * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数 第一个参数
       * handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
       * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,
       * 表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了 第三个参数handler, 我们这里将这个代理对象关联到了上方的
       * InvocationHandler 这个对象上
       */
      IBookManage proxy = (IBookManage) Proxy.newProxyInstance(target.getClass().getClassLoader(),
        target.getClass().getInterfaces(), this);
      return proxy;
     }
     @Override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 在代理真实对象前我们可以添加一些自己的操作
      System.out.println("before add book...");
      System.out.println("开始调用真实对象的方法: :" + method);
      // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
      Object result = method.invoke(target, args);
      // 在代理真实对象后我们也可以添加一些自己的操作
      System.out.println("after add book...");
      return result;
     }
    }

    代理对象最重要的方法:invoke()。

    作用:当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用

    Object invoke(Object proxy, Method method, Object[] args) throws Throwable
    
    proxy:  指代我们所代理的那个真实对象
    method:  指代的是我们所要调用真实对象的某个方法的Method对象
    args:  指代的是调用真实对象某个方法时接受的参数
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
    
    loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
    
    interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
    
    h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

    1.4 测试

    package jdkproxy;
    
    public class Client
    {
        public static void main(String[] args) {
            BookManage bookManage = new BookManage();
            DynamicProxy proxy = new DynamicProxy();
            IBookManage iBookManage = (IBookManage) proxy.bind(bookManage);
            System.out.println("代理对象的类名: " + iBookManage.getClass().getName());
            iBookManage.addBook();
        }
    }

    运行结果:

    代理对象的类名: com.sun.proxy.$Proxy0
    before add book...
    开始调用真实对象的方法: :public abstract void jdkproxy.IBookManage.addBook()
    addBook...
    after add book...

    其中com.sun.proxy.$Proxy0,就是我们代理对象的名字,它是jvm运行时动态生成的一个对象。

    那为什么Proxy.newProxyInstance()方法可以转型为IBookManage?因为这个方法里面的第二个参数给代理对象提供的是我们真实对象所实现的接口,所以代理对象实现了这组接口,当然可以转化为IBookManage类型了。然后就是代理对象(IBookManage).方法(addBook)调用。

    通过代理对象调用方法的时候,其实就是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的

    弊端:

    1. 如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。

    2. 如果业务实现类中新增了接口中没有的方法,这些方法是无法被代理的(因为无法被调用)。

    二.CGLIB动态代理(针对类)

    2.1 创建一个类不需要实现任何接口

     2.2 绑定一个业务类

    • 创建一个加强器:Enhancer enhancer = new Enhancer();//用来创建动态代理类
    • 指定加强器要代理的业务类(父类)
    • 设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦截 enhancer.setCallback(this);
    •    return enhancer.create(); // 创建动态代理类对象并返回
    // 实现回调方法 
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 
            System.out.println("预处理——————");
            proxy.invokeSuper(obj, args); //调用业务类(父类中)的方法
            System.out.println("调用后操作——————");
            return null; 
        }

    2.3 测试:创建业务类和代理类对象,然后通过  代理类对象.getInstance(业务类对象)  返回一个动态代理类对象(它是业务类的子类,可以用业务类引用指向它)。最后通过动态代理类对象进行方法调用。

    三.比较

    静态代理是通过在代码中显式定义一个业务实现类一个代理,在代理类中对同名的业务方法进行包装,用户通过代理类调用被包装过的业务方法;

    JDK动态代理是通过接口中的方法名,在动态生成的代理类中调用业务实现类的同名方法;

    CGlib动态代理是通过继承业务类,生成的动态代理类是业务类的子类,通过重写业务方法进行代理;不能对 final类进行继承

     源码级分析见:

    https://blog.csdn.net/john_lw/article/details/79539070

    http://ifeve.com/jdk动态代理代理与cglib代理原理探究/

    https://www.jianshu.com/p/9a61af393e41?from=timeline&isappinstalled=0

  • 相关阅读:
    线程池:
    C#:实现托盘
    Linux内核 TCP/IP、Socket参数调优
    redis配置文件redis.conf参数说明
    redis安装与参数说明
    巧用linux服务器的/dev/shm/,如果合理使用,可以避开磁盘IO不给力,提高网站访问速度。
    mkdir:批量创建文件夹
    linux下的shell运算(加、减、乘、除)
    Linux查看文件编码格式及文件编码转换
    解决vi/vim中粘贴会在行首多很多缩进和空格的问题
  • 原文地址:https://www.cnblogs.com/lingluo2017/p/10283674.html
Copyright © 2011-2022 走看看