代理模式

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

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

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

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

  在代码实现中相当于为一个委托对象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则是直接修改字节码。

原文地址:https://www.cnblogs.com/SacredOdysseyHD/p/8361024.html