• 代理模式


            代理模式是一种结构型设计模式,它可以为其他对象提供一种代理以控制对这个对象的访问。   

      所谓代理,是指具有与被代理对象相同的接口的类,客户端必须通过代理与被代理的目标类进行交互,而代理一般在交互的过程中(交互前后),进行某些特定的处理。

      代理模式中的结构图如下:

      代理的模式在平时生活中也很常见,比如买火车票这件小事,黄牛相当于是火车站的代理,我们可以通过黄牛或者代售点进行买票行为,但只能去火车站进行改签和退票,因为只有火车站才有改签和退票的方法。

      在代码实现中相当于为一个委托对象realSubject提供一个代理对象proxy,通过proxy可以调用 realSubject的部分功能(买票),并添加一些额外的业务处理(收取手续费),同时可以屏蔽 realSubject中未开放的接口(改签和退票)。

      代理模式有分为两种:静态代理和动态代理。

    静态代理

       指代理对象和目标对象(委托对象)在代理之前是确定的,他们都实现相同的接口或者继承相同的抽象类。

      现在模拟一个场景,一辆汽车有一个行驶的方法。

            1、定义一个行驶方法的接口 Moveable 

    public interface Moveable {
        void move();
    }

      2、定义 Car 类并实现 Moveable 接口

    public class Car implements Moveable {
        @Override
        public void move() {
            //实现开车
            try {
                Thread.sleep(new Random().nextInt(1000));
                System.out.println("汽车行驶中....");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    } 

      现在需求是,通过代理的方式实现记录汽车行驶的时间。

    静态代理实现方法

      (1)继承法:代理类直接【继承】被代理类,实现其原有方法,并添加一些额外功能

    public class CarProxyExtends extends Car {
        @Override
        public void move() {
            long starttime = System.currentTimeMillis();
            System.out.println("汽车开始行驶....");
            super.move();
            long endtime = System.currentTimeMillis();
            System.out.println("汽车结束行驶....  汽车行驶时间:" 
                    + (endtime - starttime) + "毫秒!");
        }
    }

      (2)聚合方法:目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑。

    public class CarProxyPolymer implements Moveable {
        public CarProxyPolymer (Car car) {
            super();
            this.car = car;
        }
        private Car car;
        @Override
        public void move() {
            long starttime = System.currentTimeMillis();
            System.out.println("汽车开始行驶....");
            car.move();
            long endtime = System.currentTimeMillis();
            System.out.println("汽车结束行驶....  汽车行驶时间:" 
                    + (endtime - starttime) + "毫秒!");
        }
    }
    public static void main(String[] args) {
            //使用继承方式
            Moveable m1 = new CarProxyExtends();
            m1.move();
            //使用聚合方式实现
            Car car = new Car();
            Moveable m2 = new CarProxyPolymer(car);
            m2.move();
        }
    测试代码 

      通过上面的实现进行两种方式的对比(继承方式和聚合方式)
      聚合的方式实现静态代理是合适的更好的方法,也就是代理类和被代理类同样实现同一个接口,然后代理类中存入一个被代理类的成员变量,真正调用的是这个成员变量的方法,这样可以实现多种功能的叠加,而且调整功能的顺序操作也会很简单,只需要客户端调整调用功能的顺序即可,如果采用继承的方式,就必须要实现多个功能顺序不同的代理类,这样代理类的数量会越来越多,不利于后面的维护工作。

      实际应用中很少采用静态代理的实现模式,因为一个委托类对应一个代理类,代理类在编译期间就已经确定,随着委托类方法数量越来越多,代理类的代码量是十分庞大的并且代码的重复性高。所以引入动态代理来解决此类问题。

    动态代理

      动态代理中,代理类并不是在Java代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法调用次数、添加日志功能等等,动态代理分为jdk动态代理和cglib动态代理。

    JDK 动态代理

      代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑。
      JDK动态代理只能针对实现了接口的类生成代理。

      下面通过jdk动态代理实现方法去记录车行驶的时间。

      1、利用 java.lang.reflect.Proxy类和 java.lang.reflect.InvocationHandler接口定义代理类的实现。

    public class TimeHandlerProxy implements InvocationHandler {
    
        public TimeHandler(Object target) {
            super();
            this.target = target;
        }
    
        private Object target;
        
        /*
         * 参数:
         * proxy  被代理对象
         * method  被代理对象的方法
         * args 方法的参数
         * 
         * 返回值:
         * Object  方法的返回值
         * */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            long starttime = System.currentTimeMillis();
            System.out.println("汽车开始行驶....");
            method.invoke(target);
            long endtime = System.currentTimeMillis();
            System.out.println("汽车结束行驶....  汽车行驶时间:" 
                    + (endtime - starttime) + "毫秒!");
            return null;
        }
        // 生成代理对象
        public Object getProxy (){     
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
        
            Class<?>[] interfaces = target.getClass().getInterfaces();
            return  Proxy.newProxyInstance(loader,interfaces,this);
        }
    }

      2、代理类调用方法

    public static void main(String [] agrs){
        Car c = new Car();
        TimeHandlerProxy thp = new TimeHandlerProxy(c);  
        Moveable m = (Moveable)thp.getProxy();
        m.move();
    } 
    //输出结果:
    汽车开始行驶....
    汽车结束行驶....  汽车行驶时间:744

      代理对象的生成过程由Proxy类的newProxyInstance方法实现,分为3个步骤:
      1、ProxyGenerator.generateProxyClass方法负责生成代理类的字节码
      2、native方法 Proxy.defineClass0负责字节码加载的实现,并返回对应的Class对象。
      3、利用 clazz.newInstance反射机制生成代理类的对象;
      4、接口类型引用代理对象,接口调用的方法(接口实现的多态) 

      也就是说,所谓动态代理(Dynamic Proxy)是这样一种class
      他是在运行时生成的class
      该class需要实现一组interface
      使用动态代理类时,必须实现InvocationHandler接口
      1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法
      2.创建被代理的类以及接口
      3.调用Proxy的静态方法,创建一个代理类---newProxyInstance(ClassLoader loader,Class[]interfaces,InvocationHandler h)
      4.通过代理调用类方法

      JDK动态代理局限性
      通过反射类 Proxy 和 InvocationHandler回调接口实现的jdk动态代理,要求委托类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用JDK动态代理,但是我们可以使用CGLIB动态代理来实现。

    CGLIB动态代理

      CGLIB(CODE GENERLIZE LIBRARY)代理是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的所有方法,所以该类或方法不能声明称final的。

         使用CgLib动态代理需要引入cglib-nodep.jar包    https://github.com/cglib/cglib/releases

    定义没有实现接口的一个Train的类,通过动态代理实现日志记录的功能。 

    class Train{
        public void move() {
            //实现开车
            try {
                Thread.sleep(new Random().nextInt(1000));
                System.out.println("火车行驶中....");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    } 
    Train

      下面通过一个例子看看如何实现动态代理。

      1、实现 MethodInterceptor接口,定义方法的拦截器,2、利用 Enhancer类生成代理类;

    public class CglibProxy implements MethodInterceptor {
        /**
         *    定义方法的拦截器
         * 拦截所有目标类方法的调用
         * obj  目标类的实例
         * m   目标方法的反射对象
         * args  方法的参数
         * proxy代理类的实例
         */
        @Override
        public Object intercept(Object obj, Method m, Object[] args,
                MethodProxy proxy) throws Throwable {
            System.out.println("日志开始...");
            //代理类调用父类的方法
            proxy.invokeSuper(obj, args);
            System.out.println("日志结束...");
            return null;
        }  
        private Enhancer enhancer = new Enhancer();
        //Enhancer类生成代理类
        public Object getProxy(Class clazz){
            //设置创建子类的类
            enhancer.setSuperclass(clazz);
            enhancer.setCallback(this);
            return enhancer.create();
        }
    }

      3、通过代理类调用方法执行

    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        Train t = (Train)proxy.getProxy(Train.class);
        t.move();
    }

      代理对象的生成过程由Enhancer类实现,大概步骤如下:

      1、生成代理类Class的二进制字节码;
      2、通过 Class.forName加载二进制字节码,生成Class对象;
      3、通过反射机制获取实例构造,并初始化代理类对象。
      4、委托类类型引用代理对象,调用方法(继承实现的多态)

    总结:

      JDK和CGLIB动态代理的区别
        1、JDK动态代理生成的代理类和委托类实现了相同的接口;
        2、CGLIB动态代理中生成的字节码更加复杂,生成的代理类是委托类的子类,且不能处理被 final 关键字修饰的方法; 
        3、JDK采用反射机制调用委托类的方法,CGLIB采用类似索引的方式直接调用委托类方法;

    扩展:

      Spring框架是时下很流行的Java开源框架,Spring之所有如此流行,跟它自身的特性是分不开的。Spring本身含有两大特性,一个是IOC,一个是AOP的支持。

             IOC是Inverse Of Control,即控制反转,也有人把IOC称作依赖注入。我觉得依赖注入这种说法很好理解,但不完全对。依赖注入是Dependency Injection的缩写,是实现IOC的一种方法,但不等同于IOC,IOC是一种思想,DI只是一种实现。

               AOP是Aspect Oriented Programming的缩写,即面向切面编程。与面向过程和面向对象的编程方式相比,面向切面编程提供了一种全新的思路,解决了OOP编程过程中的一些痛点。 IOC的实现原理是利用了JAVA的反射技术,那么AOP的实现原理是什么呢?——动态代理技术

      目前动态代理技术主要分为Java自己提供的JDK动态代理技术和CGLIB技术。Java自带的JDK动态代理技术是需要接口的,而CGLIB则是直接修改字节码。

  • 相关阅读:
    10_树基础部分
    11_多线程
    14_JUC
    Servlet代码实例
    关于JSP引用资源路径
    Struts2代码实例
    hadoop三大核心组件介绍
    presto自定义函数开发
    git使用
    hive优化总结
  • 原文地址:https://www.cnblogs.com/SacredOdysseyHD/p/8361024.html
Copyright © 2020-2023  润新知