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

前言:
代理模式在是一个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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容

  • 参考资料:菜鸟教程之设计模式 设计模式概述 设计模式(Design pattern)代表了最佳的实践,通常被有经验...
    Steven1997阅读 1,173评论 1 12
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,654评论 18 139
  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,939评论 1 15
  • 真诚的,TNANKS。 个人Github-23种设计模式案例链接 创建型模式 工厂模式 工厂模式(Factory ...
    水清_木秀阅读 26,055评论 11 204
  • 最近借口工作几近丢了锻炼的习惯,胖了好多。都不好意思回家见女朋友了,为了还能敞开吃,为了身材。天天晨跑吧,一个月。...
    惠夕安阅读 191评论 2 1