设计模式-代理模式(Proxy Pattern)

一、概念

为目标对象提供一种代理,以控制对其的访问。代理对象相当于中介,可以去掉功能服务或者增加添加额外功能服务。
举例
今天我想去服装店A买件衣服,一般都是直接去A的实体店进行购买,但是今天下雨了不想出门,于是我就打开淘宝在A的官方旗舰店B把想要的衣服买了,那么B就是相当于A的一个代理。B又提供了其它一些服务,比如:七天无理由退换货、送货上门等(添加额外功能)。B又没有现场试穿等服务(去掉功能服务)。

1.1 作用

代理可以在不改变目标类原有代码的基础上拓展其功能。例如:对一些方法进行性能测试,添加操作日志等。AOP便是使用动态代理来实现拦截切入功能。

二、分类

2.1 静态代理

2.1.1 特点:

需要定义父类或者接口,代理对象和被代理对象需要同时继承父类或者实现该接口。

2.1.2 缺点:

每进行一个代理都要建立一个代理类,随着代理类增多会造成类膨胀。

2.1.3 实现:

有两种实现方式,继承与聚合,由于聚合的方式优于继承,这里只演示聚合的实现。
创建接口:目标类和代理类同时实现该接口。

public interface Moveable {
    void move();
}

目标对象

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

代理对象

public class CarProxy implements Moveable {
    private Car car;

    public CarProxy(Car car) {
        this.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 class ProxyTest {
    public static void main(String[] args) {
        Car car = new Car();
        Moveable proxy = new CarProxy(car);
        proxy.move();
    }
}

测试结果

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

2.2 jdk动态代理

2.2.1 特点:

  1. 代理目标必须实现一个或者多个接口。
  2. 代理对象的生成不需要实现接口。
  3. 代理对象通过JAVA的API动态生成。

2.2.2 缺点:

没有实现接口的类不能进行动态代理。

2.2.3 实现:

Java动态代理类位于java.lang.reflect包下,主要涉及以下两个类
(1) public interface InvocationHandler:该接口定义了一个抽象方法:

/**
 * @param proxy 代理类
 * @param method 被代理的方法
 * @param args 被代理方法的参数
 */
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;

该抽象方法在代理类中动态实现。
(2)public class Proxy:该类为动态代理类,该类产生代理类的方法为:

 /**
     * @param loader 目标对象的类加载器
     * @param interfaces 目标对象的接口类型
     * @param h 事件处理函数,实现对目标对象的操作。
     */
    public static Object newProxyInstance(
            ClassLoader loader,
            Class<?>[] interfaces,
            InvocationHandler h) throws IllegalArgumentException

创建接口

public interface Moveable {
    void move();
}

目标对象

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

代理工厂类

public class TimeProxyFactory {
    //代理的目标对象
    private Object target;

    public TimeProxyFactory(Object target) {
        this.target = target;
    }

    //获取目标对象
    public Object getInstance() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        long startTime = System.currentTimeMillis();
                        System.out.println("汽车开始行驶......");
                        Object obj = method.invoke(target, args);
                        long endTime = System.currentTimeMillis();
                        System.out.println("汽车结束行驶......  汽车行驶时间:"+(endTime-startTime)+"毫秒!");
                        return obj;
                    }
                });
    }
}

测试

public class ProxyTest {
    public static void main(String[] args) {
        Moveable move = new Car();
        TimeProxyFactory factory = new TimeProxyFactory(move);
        Moveable proxy =(Moveable) factory.getInstance();
        proxy.move();
    }
}

测试结果

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

2.3 CGLIB动态代理

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

2.3.1 特点:

  1. Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
  2. Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.
  3. 因为它是使用继承方式,所以不能对final修饰的类进行代理。
  4. 产生的代理类其实就是目标类的子类。

2.3.2 实现:

目标对象

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

代理类

public class CGLibProxy implements MethodInterceptor {
    //创建代理对象
    public Object getProxyInstance(Class c) {
        //Enhancer可以为接口或者非接口类型创建一个子类作为代理类,可以拦截所有方法
        Enhancer enhancer = new Enhancer();
        //设置创建子类的类,也就是说为每个代理类创建子类
        enhancer.setSuperclass(c);
        //设置回掉函数(因为MethodInterceptor继承了Callback类,默认执行intercept方法)
        enhancer.setCallback(this);
        //创建子类实例,返回去
        return enhancer.create();
    }

    /**
     * 拦截所有目标类方法的调用
     * @param o       目标类的实例
     * @param method  目标方法的反射对象
     * @param objects 目标方法的参数
     * @param proxy   代理类的实例
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy proxy) throws Throwable {

        long startTime = System.currentTimeMillis();
        System.out.println("汽车开始行驶......");
        //代理类调用父类的方法,来实现代理类的方法
        Object returnObject = proxy.invokeSuper(o, objects);
        long endTime = System.currentTimeMillis();
        System.out.println("汽车结束行驶......  汽车行驶时间:"+(endTime-startTime)+"毫秒!");
        return returnObject;
    }
}

测试

public class ProxyTest {
    public static void main(String[] args) {
        Train train = (Train) new CGLibProxy().getProxyInstance(Train.class);
        train.move();
    }
}

测试结果

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

三、小结

代理方式特点缺点
静态代理需要定义父类或者接口,代理对象和被代理对象需要同时继承父类或者实现该接口,一次代理一个类随着代理类增多,出现大量重复代码,难维护,造成类膨胀
jdk动态代理目标类需要实现至少一个接口,代理对象通过JAVA的API动态生成,可以代理一个借口的多个实现只能够代理实现了接口的目标类
cglib动态代理代理类要实现MethodInterceptor接口,通过Enhancer创建目标类的子类为代理对象,所有也是通过继承关系创建代理类的,然后通过实现intercept(Object o, Method method, Object[] objects, MethodProxy proxy)方法对所有的方法进行拦截,添加增强处理,注意该方法中要通过代理类的invokeSuper调用父类的方法不能代理final修饰的类
只有把命运掌握在自己手中,从今天起开始努力,即使暂时看不到希望,也要相信自己。因为比你牛几倍的人,依然在努力。
原文地址:https://www.cnblogs.com/freesky168/p/14358232.html