代理模式

1. 什么是代理模式

代理模式就是为其他对象提供一种代理以控制对这个对象的访问,代理对象起到中介的作用,可以去掉功能服务或者增加额外的服务(比如火车票代售处)。
代理模式有着广泛的应用,比如Spring中的AOP、事务,又比如日志分析、性能监控等等,这些场景都有一个共同的特性,就是需要在调用某段代码的前后增加一些操作,从而实现额外的功能。

代理一般可以分为:

  1. 远程代理:比如客户端服务器模式
  2. 虚拟代理:比如网站图片延迟加载
  3. 保护代理:比如控制用户的访问权限
  4. 智能引用代理:比如火车票代售处

本文主要讲述智能引用代理,而智能引用代理在实现上又可以分为静态代理和动态代理。

2. 静态代理

什么叫静态代理呢?简单来说,就是代理类在代理之前是确定的,它和被代理类实现了相同的接口或者继承自被代理类。
静态代理在实现上又可以分为继承方式和组合方式,在介绍这两种方式之前我们先来看下如果不用代理的话代码会怎么写

2.1 没用代理模式之前

2.1.1 Moveable接口

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public interface Moveable {

    void move();
}

2.1.2 Car实现类

package proxy;

import java.util.Random;

/**
 * @author zhaobin11@baidu.com
 */
public class Car implements Moveable {

    @Override
    public void move() {
        long start = System.currentTimeMillis();
        System.out.println("汽车开始行驶...");

        try {
            Thread.sleep(new Random().nextInt(1000));
            System.out.println("汽车行驶中...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("汽车结束行驶...汽车行驶时间:" + (System.currentTimeMillis() - start) + "毫秒!");
    }
}

2.1.3 Test测试类

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public class Test {

    public static void main(String[] args) {
        Car car = new Car();
        car.move();
    }
}

运行Test,可以看到

汽车开始行驶...
汽车行驶中...
汽车结束行驶...汽车行驶时间:201毫秒!

可以看到在Car类move方法中的汽车行驶前后都记录了时间,根据前后时间可以算出汽车行驶消耗的时间,类似这样的在某段代码的前后增加一些操作,在我们平时的开发过程中会经常遇到。那能不能把调用的代码与它前后的额外代码分离开来呢?代理模式就是解决这个问题的。

2.2 用了代理模式(继承)之后

2.2.1 Moveable接口

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public interface Moveable {

    void move();
}

2.2.2 Car实现类

package proxy;

import java.util.Random;

/**
 * @author zhaobin11@baidu.com
 */
public class Car implements Moveable {

    @Override
    public void move() {
        try {
            Thread.sleep(new Random().nextInt(1000));
            System.out.println("汽车行驶中...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.2.3 CarTimeProxy类

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public class CarTimeProxy extends Car {

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

2.2.4 Test测试类

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public class Test {

    public static void main(String[] args) {
        Moveable m = new CarTimeProxy();
        m.move();
    }
}

运行Test,可以看到

汽车开始行驶...
汽车行驶中...
汽车结束行驶...汽车行驶时间:490毫秒!

继承方式的静态代理就是写一个代理类,继承被代理类,然后在代理类重写的方法中调用被代理类的方法,并在前后加上时间记录。继承方式实现的静态代理有个致命的缺陷就是扩展性差,假设现在希望在记录行驶时间前后再套上一层日志的功能,按照继承的方式我们需要写一个CarLogProxy类去继承CarTimeProxy,然后重写CarTimeProxy的move方法,调用CarTimeProxy的move方法并在前后在上日志功能,最后运行Test,就可以看到

日志开始...
汽车开始行驶...
汽车行驶中...
汽车结束行驶...汽车行驶时间:372毫秒!
日志结束...

那假如现在又想要记录汽车行驶时间的功能在日志统计功能的外面呢?也就是这样

汽车开始行驶...
日志开始...
汽车行驶中...
日志结束...
汽车结束行驶...汽车行驶时间:372毫秒!

这时候我们发现之前的代码都用不了了,需要额外写个CarLogProxy2类继承Car,然后在重写的方法中调用Car的move方法并在前后加上日志功能。然后再额外写个CarTimeProxy2类去继承CarLogProxy2,在重写的方法中调用CarLogProxy2的move方法并在前后加上行驶时间统计功能。
可以发现,继承方式实现的静态代理在面对扩展时会出现大量的重复类和代码,冗余度高,所以一般不用。

2.3 用了代理模式(组合)之后

2.3.1 Moveable接口

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public interface Moveable {

    void move();
}

2.3.2 Car实现类

package proxy;

import java.util.Random;

/**
 * @author zhaobin11@baidu.com
 */
public class Car implements Moveable {

    @Override
    public void move() {
        try {
            Thread.sleep(new Random().nextInt(1000));
            System.out.println("汽车行驶中...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.3.3 CarTimeProxy类

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public class CarTimeProxy implements Moveable {

    private Car car;

    public CarTimeProxy(Car car) {
        super();
        this.car = car;
    }

    @Override
    public void move() {
        long start = System.currentTimeMillis();
        System.out.println("汽车开始行驶...");
        car.move();
        System.out.println("汽车结束行驶...汽车行驶时间:" + (System.currentTimeMillis() - start) + "毫秒!");
    }
}

2.3.4 Test测试类

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public class Test {

    public static void main(String[] args) {
        Moveable m = new CarTimeProxy(new Car());
        m.move();
    }
}

组合方式实现的静态代理就是我们写一个代理类,这个代理类实现和被代理类一样的接口,在代理类的初始化时,需要传入一个被代理类的引用,然后在重写的方法中调用被代理类的方法并在前后加上行驶时间记录功能即可。
与继承方式相比,组合的方式更具有扩展性,还是以之前继承方式遇到的问题举例子,也就是换成在记录汽车行驶的时间外面加上日志功能

2.4 加上日志功能

2.4.1 Moveable接口

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public interface Moveable {

    void move();
}

2.4.2 Car实现类

package proxy;

import java.util.Random;

/**
 * @author zhaobin11@baidu.com
 */
public class Car implements Moveable {

    @Override
    public void move() {
        try {
            Thread.sleep(new Random().nextInt(1000));
            System.out.println("汽车行驶中...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.4.3 CarTimeProxy类

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public class CarTimeProxy implements Moveable {

    private Moveable m;

    public CarTimeProxy(Moveable m) {
        super();
        this.m = m;
    }

    @Override
    public void move() {
        long start = System.currentTimeMillis();
        System.out.println("汽车开始行驶...");
        m.move();
        System.out.println("汽车结束行驶...汽车行驶时间:" + (System.currentTimeMillis() - start) + "毫秒!");
    }
}

2.4.4 CarLogProxy类

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public class CarLogProxy implements Moveable {

    private Moveable m;

    public CarLogProxy(Moveable m) {
        super();
        this.m = m;
    }

    @Override
    public void move() {
        System.out.println("日志开始...");
        m.move();
        System.out.println("日志结束...");
    }
}

2.4.5 Test测试类

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public class Test {

    public static void main(String[] args) {
        CarTimeProxy carTimeProxy = new CarTimeProxy(new Car());
        CarLogProxy carLogProxy = new CarLogProxy(carTimeProxy);
        carLogProxy.move();
    }
}

运行Test,可以看到

日志开始...
汽车开始行驶...
汽车行驶中...
汽车结束行驶...汽车行驶时间:372毫秒!
日志结束...

如果我们想要输出结果为

汽车开始行驶...
日志开始...
汽车行驶中...
日志结束...
汽车结束行驶...汽车行驶时间:372毫秒!

那么只需要在调用代理类时换下顺序即可,也就是Test类里面这样写

public static void main(String[] args) {
    CarLogProxy carLogProxy = new CarLogProxy(new Car());
    CarTimeProxy carTimeProxy = new CarTimeProxy(carLogProxy);
    carTimeProxy.move();
}

相比继承方式来说组合方式更加的灵活,但是问题来了,如果这会汽车变成火车了,难道又要重写一个TrainTimeProxy和TrainLogProxy吗,能不能只写一个TimeProxy和LogProxy实现对各种车的代理呢?

3. 动态代理

动态代理在实现上分为JDK动态代理和cglib动态代理

3.1 JDK动态代理

只能代理实现了接口的类,没有实现接口的类不能实现JDK的动态代理
动态代理实现步骤:

  1. 创建一个实现接口InvocationHandler的类,它必须实现invoke方法
  2. 创建被代理的类以及接口
  3. 调用Proxy的静态方法,创建一个代理类newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
  4. 通过代理调用方法

3.1.1 Moveable接口

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public interface Moveable {

    void move();
}

3.1.2 Car实现类

package proxy;

import java.util.Random;

/**
 * @author zhaobin11@baidu.com
 */
public class Car implements Moveable {

    @Override
    public void move() {
        try {
            Thread.sleep(new Random().nextInt(1000));
            System.out.println("汽车行驶中...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3.1.3 TimeHandler类

package proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @author zhaobin11@baidu.com
 */
public class TimeHandler implements InvocationHandler {

    private Object target;

    public TimeHandler(Object target) {
        super();
        this.target = target;
    }

    @Override
    // proxy:代理类    method:被代理的方法    args:方法的参数数组
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("汽车开始行驶...");
        method.invoke(target);
        System.out.println("汽车结束行驶...汽车行驶时间:" + (System.currentTimeMillis() - start) + "毫秒!");
        return null;
    }
}

3.1.4 Test测试类

package proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * @author zhaobin11@baidu.com
 */
public class Test {

    public static void main(String[] args) {
        Car car = new Car();
        InvocationHandler h = new TimeHandler(car);
        // loader:类加载器    interfaces:实现接口    h:InvocationHandler接口实现类
        Moveable m = (Moveable) Proxy.newProxyInstance(car.getClass().getClassLoader(), car.getClass().getInterfaces(), h);
        m.move();
    }
}

运行Test,可以看到

汽车开始行驶...
汽车行驶中...
汽车结束行驶...汽车行驶时间:50毫秒!

3.2 cglib动态代理

cglib动态代理是针对类来实现代理的,对指定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用(继承实现,不能代理被final修饰的类)
需要导入一个名为cglib-nodep-2.2.jar的包

3.2.1 Train类

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public class Train {

    public void move() {
        System.out.println("火车行驶中...");
    }
}

3.2.2 CglibProxy类

package proxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author zhaobin11@baidu.com
 */
public class CglibProxy implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class clazz) {
        // 设置创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    // 拦截所有目标类方法的调用    obj:目标类的实例    m:目标方法的反射对象    args:方法的参数    proxy:代理类的实例
    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;
    }
}

3.2.3 Test测试类

package proxy;

/**
 * @author zhaobin11@baidu.com
 */
public class Test {

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

运行Test,可以看到

日志开始...
火车行驶中...
日志结束...
posted @ 2020-06-05 16:38  sakura1027  阅读(153)  评论(0编辑  收藏  举报