我爱编程

一起来学习设计模式:代理模式

2018-05-26  本文已影响181人  __y

前言:
代理模式在是一个java中常用的设计模式,离我们较近的有Mybatis,spring等。学会代理模式的设计思想对我们理解框架的设计有极大帮助。
在我们学习代理模式之前要有以下的基础知识:
1.面向对象的设计思维
2.多态的知识
3.反射的知识
如果这些还不掌握的话要先去补补哦!
好了废话不多说下面开始一起学习代理模式

1.代理模式的基本概念

什么是代理模式呢:我们举个简单的例子,比如我们要买火车票,我们可以不去火车站,通过中间的一些途径去买票,当然我们在买票的过程中要把自己的一些信息给代理商,让他知道我们的目的地等一些信息。但是,如果我们想退票了,就只能去火车站退票了。
定义:
为其他对象提供了一种代理以控制对这个对象的访问。代理对象起到中介作用,可去掉功能服务或增加额外服务。

代理模式的分类:

2.静态代理的概念和代码实现

我们用开车的例子来实现,现在我们想记录开车的时间(智能代理,增加额外的服务),先用普通的方法实现
创建一个接口

public interface Moveable {
    void move();
}

创建车的类,实现上面的接口

import java.util.Random;

public class Car implements Moveable {
    @Override
    public void move() {
        long startTime = System.currentTimeMillis();
        System.out.println("开始运行时间" + startTime);
        //实现开车
        try {
            Thread.sleep(new Random().nextInt(1000));
            System.out.println("汽车行驶中");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("结束运行时间" + (endTime - startTime) + "毫秒");
    }
}

来看看效果

public class Client {
    public static void main(String[] args) {
        Car car = new Car();
        car.move();
    }
}
image.png
上面是普通的实现方式,下面我们用继承的方式实现代理
改一下Car的代码,把额外的业务功能去掉
public class Car implements Moveable {
    @Override
    public void move() {
        //实现开车
        try {
            Thread.sleep(new Random().nextInt(1000));
            System.out.println("汽车行驶中");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

创建Car2

public class Car2 extends  Car {
    @Override
    public void move() {
        //将父类的的一些业务逻辑代码分离出来
        long startTime = System.currentTimeMillis();
        System.out.println("开始运行时间" + startTime);
        //调用父类的方法
        super.move();
        long endTime = System.currentTimeMillis();
        System.out.println("结束运行的时间" + endTime);
        System.out.println("行驶时间" + (endTime - startTime) + "毫秒");
    }
}

public class Client {


    public static void main(String[] args) {
//        //一般做法
//        Car car = new Car();
//        car.move();
        //集成模式(采用继承的方式)
          Moveable m = new Car2();
          m.move();


结果:


image.png

下面我们用聚合的方式实现代理
扫盲:所谓聚合,简单来说就是在一个类当中调用另一个对象。
创建Car3,实现Moveble接口

public class Car3 implements Moveable {
    private Car car;
    //构造方法
    public Car3(Car car) {
        this.car = car;
    }
    @Override
    public void move() {
        //增加额外的功能
        long startTime = System.currentTimeMillis();
        System.out.println("开始运行时间" + startTime);
        car.move();
        long endTime = System.currentTimeMillis();
        System.out.println("结束运行的时间" + endTime);
        System.out.println("行驶时间" + (endTime - startTime) + "毫秒");
    }
}

public class Client {


    public static void main(String[] args) {
//        //一般做法
//        Car car = new Car();
//        car.move();
        //继承模式(采用继承的方式)
//        Moveable m = new Car2();
//        m.move();
        //聚合的方式
        Car car = new Car();
        Moveable m = new Car3(car);
        m.move();
    }
}

结果:


image.png

哪个更好?继承or聚合?

我们在上面可以看到,从普通的方式到用两种代理模式结果都是一样的,那究竟哪一种更适合代理模式呢?
我们来设计一个场景:
上文我们看到已经有记录时间的功能了,那如果我想记录汽车的日志功能呢。利用集成继承的方式的话,我们可能需要再写一个Car4类,然后继承Car2,同时增加自己的业务功能。那后面,我又想改需求了,想先日志,再时间,我们又要再写一个类去继承Car2,然后写自己的业务逻辑。这样后面的业务越来越多的时候,如下图:


image.png

我们看到类会越来越多,越来越臃肿。
所以,我们推荐用聚合的方式,那聚合的方式真的方便吗?我们看看代码的实现。
记录时间:

public class CarTimeProxy implements Moveable {
    private Moveable m;
    //构造方法
    public CarTimeProxy(Moveable m) {
        this.m = m;
    }
    @Override
    public void move() {
        //增加额外的功能
        long startTime = System.currentTimeMillis();
        System.out.println("开始运行时间" + startTime);
        m.move();
        long endTime = System.currentTimeMillis();
        System.out.println("结束运行的时间" + endTime);
        System.out.println("行驶时间" + (endTime - startTime) + "毫秒");
    }
}

记录日志

public class CarLogProxy implements Moveable{
    private Moveable m;
    //构造方法
    public CarLogProxy(Moveable m) {
        this.m = m;
    }
    @Override
    public void move() {
        //增加额外的功能
        System.out.println("汽车开始行驶" );
        m.move();
        System.out.println("汽车结束行驶");
    }
}


来,需求来了。先记录时间,再记录日志

public class Client {


    public static void main(String[] args) {
        Car car = new Car();
        //先记录时间
        CarTimeProxy ctp = new CarTimeProxy(car);
        //记录日志
        CarLogProxy clp = new CarLogProxy(ctp);
        clp.move();

    }
}

结果:


image.png

这时候,我又想改了,先来日志,再来时间吧

public class Client {


    public static void main(String[] args) {
        Car car = new Car();
        //记录日志
        CarLogProxy clp = new CarLogProxy(car);
        //记录时间
        CarTimeProxy ctp = new CarTimeProxy(clp);
        ctp.move();

    }
}

image.png

OK! so easy!
所以,是不是聚合模式下的代理模式更适合我们实际中的开发呢?! 答案是肯定的!
好了,我们上面的记录的是普通车的,现在我像将这项技术应用到火车上面去了,那这时候难道又要增加火车的记录时间和记录日志类吗? 会不会有点麻烦呢?有没有偷懒一点的方法呢?有!接下来看看动态的代理模式

3.动态代理模式

JDK动态代理模式

所谓的JDK动态代理是这样一种class:在运行的时候生成的class,需要一组interface,使用动态代理类的时候,必须实现InvocationHandler接口


image.png

1.Interface InvocationHandler:该接口定义了一个方法public object invoke(Object obj,Method method,Object[] args) : 第一个参数指的是代理类,method指的是被代理的方法,args为该方法的参数数组,这个抽象方法在代理中动态实现
2.Proxy:这是动态代理类
static Object new ProxyInstance(ClassLoader loader, Class[] interfaces,InvovationHandler h) :
返回一个代理类的实例,返回后的代理类可以当作被代理类使用(可以使用被代理类的在接口声明中的方法)


代码实现:

package com.test.jdkproxy;

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

public class TimeHandler implements InvocationHandler {
    //要被代理的对象
    private Object targetObject;

    public TimeHandler(Object targetObject) {
        this.targetObject = targetObject;
    }
    /*
     * 参数:
     * proxy  被代理对象
     * method  被代理对象的方法
     * args 方法的参数
     *
     * 返回值:
     * Object  方法的返回值
     * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long startTime = System.currentTimeMillis();
        System.out.println("开始运行时间" + startTime);
        method.invoke(targetObject);

        long endTime = System.currentTimeMillis();
        System.out.println("结束运行的时间" + endTime);
        System.out.println("行驶时间" + (endTime - startTime) + "毫秒");
        return null;
    }
}

测试类:

package com.test.jdkproxy;

import com.test.proxy.Car;
import com.test.proxy.Moveable;

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

public class Clinet {
    public static void main(String[] args) {
        //创建要被代理的对象
        Car car = new Car();
        Class<?> clazz = car.getClass();
        InvocationHandler handler = new TimeHandler(car);
        Moveable m = (Moveable) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),handler);
        m.move();
    }
}

结果:

image.png
实现步骤总结:
1.创建一个实现接口InvocationHandler的类,实现invoke方法;
2.创建被代理的类以及接口(Car,Moveabale);
3.调用Proxy的静态方法,创建一个代理类;
4.通过代理调用方法

cglib实现动态代理(需要引入cglib-nodep-2.2.jar)

需要代理的类

package com.test.cglibproxy;

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

代理类:

package com.test.cglibproxy;

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

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor{
    private Enhancer enhancer = new Enhancer();
    public Object getObject(Class clazz) {
        //设置创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);

        return enhancer.create();
    }

    /**
     * 拦截所有目标类方法的调用
     * 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;
    }
}

测试类

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


结果:


image.png

两者区别

image.png
上一篇下一篇

猜你喜欢

热点阅读