美文网首页我爱编程
一起来学习设计模式:代理模式

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

作者: __y | 来源:发表于2018-05-26 15:49 被阅读181次

    前言:
    代理模式在是一个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

    相关文章

      网友评论

        本文标题:一起来学习设计模式:代理模式

        本文链接:https://www.haomeiwen.com/subject/nfwujftx.html