代理模式

代理模式

在正式介绍代理模式之前,我们需要思考一下,我们为什么需要使用到代理类,如果可以直接访问和使用到目标资源对象,我们干嘛还要脱裤子放屁找一个代理类呢?

问题就出在这一点,有一些场景下,我们确实无法直接访问到目标资源,例如:

  • 出于安全性的考虑,目标资源不能直接暴露给最终用户
  • 出于易用性的考虑,目标资源过于复杂,很多接口是用户不会使用到的,直接使用会增大用户使用成本
  • 出于监控、审计等方面的考虑,目标资源每一次的访问都需要被监控和记录到
  • 只有达到某个规则或者要求才能访问到目标资源,而这些规则跟资源本身没有直接关联,耦合在一起也不方便各自的维护

当出现类似上述需求场景的情况下,那么你可能需要使用到代理模式了。

与装饰器模式对比

  • 装饰器模式侧重于对被装饰对象的功能增强
  • 代理模式侧重于对被代理对象的访问控制

静态代理 VS 动态代理

  • 静态代理:一个代理场景需要构造一个代理类,造成的问题是导致系统中的代理类过多
  • 动态代理:被代理的类是代码运行时指定的,JDK中可通过实现 InvocationHandler 接口来实现动态代理

JDK 动态代理也有不足之处,它要求被代理类一定要实现某个接口,比如上面的 Station 类实现了 TicketSell 接口。如果我们的类原本是没有实现接口的,总不能为了用代理而特意去给它加一个接口吧?

优缺点比较

  1. 优点
  • 扩展性强,对象更智能。
  1. 缺点
  • 代理类由于做了很多额外的操作,可能使请求速度变慢。

实例

静态代理

Calculator.java

/**
 * @Description 计算器的接口
 * @Date 2020/3/8 17:27
 **/
public interface Calculator {
    int add(int a, int b);
}

CalculatorImpl.java

/**
 * @Description 计算器接口Calculator的一个具体实现类
 * @Date 2020/3/8 17:50
 **/
public class CalculatorImpl implements Calculator {
    @Override
    public int add(int a, int b) {
        return a + b;
    }
}

CalculatorProxy.java


/**
 * @Description 计算器的代理类
 * @Date 2020/3/8 17:50
 **/
public class CalculatorProxy implements Calculator {

    private Calculator calculator;

    public CalculatorProxy(Calculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public int add(int a, int b) {

        // 具体执行前可以做的工作

        int result = calculator.add(a, b);

        // 具体执行后可以做的工作

        return result;
    }
}

单元测试

public class ProxyTest {

    @Test
    public void testStaticProxy() {
        Calculator calculator = new CalculatorImpl();
        CalculatorProxy calculatorProxy = new CalculatorProxy(calculator);
        int result = calculatorProxy.add(1, 2);
        System.out.println(result);
    }
}

在上面的代码中,我们定义了一个接口Calculator、一个具体的实现类CalculatorImpl和一个代理类CalculatorProxy。在CalculatorProxy类的实现中,我们可以看到add方法中调用了真正的实现类的add方法,并且在调用的真实方法之前和之后都有机会做一些额外的工作,例如记录日志、记录执行时间等。这种方式看上去非常直接,实现页比较方便,不过存在一个问题,即如果需要对多个类进行代理,并且即使代理类所需要实现的功能时一致的,我们依然需要为每个实现类都封装一个代理类,这里会涉及到大量的冗余代码的编写,后面这部分冗余代码一旦发生修改,改动量将会非常巨大。

使用动态代理将会帮助我们解决掉这个麻烦,下面我们具体看一下动态代理的实现方案。

动态代理

LogHandler.java

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

/**
 * @Description 日志打印处理器
 * @Author louxiujun
 * @Date 2020/3/8 18:02
 **/
public class LogHandler implements InvocationHandler {

    private Object object;

    public LogHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 模拟前置处理逻辑
        this.doBefore();

        Object result = method.invoke(object, args);

        // 模拟后置处理逻辑
        this.doAfter();

        return result;
    }

    private void doBefore() {
        System.out.println("do something before action");
    }

    private void doAfter() {
        System.out.println("do something after action");
    }
}

编写动态代理类DynamicProxy.java

import com.netease.learn.designPattern.proxy.staticProxy.Calculator;

import java.lang.reflect.Proxy;

/**
 * @Description 动态代理类
 * @Author louxiujun
 * @Date 2020/3/8 18:06
 **/
public class DynamicProxy implements Calculator {

    Calculator calculator;

    public DynamicProxy(Calculator calculator) {
        this.calculator = calculator;
    }

    @Override
    public int add(int a, int b) {
        LogHandler logHandler = new LogHandler(calculator);

        Calculator proxy = (Calculator) Proxy.newProxyInstance(calculator.getClass().getClassLoader(),
                                                                calculator.getClass().getInterfaces(),
                                                                logHandler);

        return proxy.add(a, b);
    }
}

单元测试:

public class ProxyTest {
/**
     * 动态代理测试
     */
    @Test
    public void testDynamicProxy() {
        Calculator calculator = new CalculatorImpl();
        DynamicProxy dynamicProxy = new DynamicProxy(calculator);
        int result = dynamicProxy.add(1, 2);
        System.out.println(result);
    }
}

输出结果:

do something before action
do something after action
3

从上面的代码我们可以看到动态代理的使用方式及动态代理本身的实现。通过Proxy.newProxyInstance来创建代理的方法可以为不同的委托类都创建代理类,在具体代理的实现上,所给出的是通用的实现方式,被代理的方法都会进入invoke方法中,我们可以在invoke的内部做很多的事情。从上面的代码可以看到,使用动态代理之后,对于同样的事情代理都只需要实现一次即可,就可以提供给多个不同的委托类使用了,提高了在需要使用到代理类的场景下代码的复用性和后期的可维护性。

小结

代理在设计模式这边叫代理模式,它的具体实现上通常有静态代理和动态代理之分,本身主要借助JDK提供的API给出了动态代理实现的Demo,它是借助反射JDK反射的机制来实现的,大家在后面学习Spring AOP章节的时候,还会接触到cdlib动态代理的实现,它则是通过更高级的字节码增强技术来实现的。

参考资料

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

推荐阅读更多精彩内容