设计模式 --- 代理模式(转)

本贴最后更新于 1447 天前,其中的信息可能已经时移世异

前言

只有光头才能变强

在看到 FilterInputStreamFilterOutputStream 时看到了之前常听见的装饰模式(对 IO 一定了解的同学可能都会知道那么一句话:在 IO 用得最多的就是装饰模式了)!

看到这里你以为我要讲装饰模式了么?不是,今天我们来讲讲什么是代理模式(就是这么皮,装饰模式明天讲吧~)。

受知乎 @Beautiful Java 文章和《设计模式之禅》的启发,我也来搞一篇脑洞小开的文章..

那么接下来就开始吧,如果文章有错误的地方请大家多多包涵,不吝在评论区指正哦~

声明:本文使用 JDK1.8

一、代理模式介绍

代理模式是一种非常好理解的一种设计模式,生活中处处都有代理

  • 王宝强作为一个明星,不可能什么事都由他自己干(约电视剧、排期之类的),于是他请了经纪人
  • 去医院挂号很麻烦怎么办?找黄牛帮我们挂号
  • 王者荣耀技术水平不够,想要上分怎么办?请游戏代练
  • 写点不正经的代码被警察捉走了怎么办?请律师帮我们打官司

无论是经纪人、黄牛、游戏代练、律师他们都是得帮我们干活。但是他们不能一手包办的,仅仅在“我”的基础上处理一些杂碎的东西(我们不愿意干、或者干不了的东西)。

  • 导演找了黄宝强的经纪人让王宝强去拍电影
  • 黄牛去排队让我们能挂上号
  • 游戏代练上分是我的微信账号
  • 律师帮我处理法律上的问题,如果打官司失败,牢还是由我来坐

再举个例子:

  • 现在我是一个网红,拥有很多粉丝。粉丝希望我天天写代码给他们看,那我肯定不能天天写代码啊,我岂不是很忙....于是乎,我就去找了个经纪人。这个经纪人就代表了我。当忠实粉丝想要我写代码的时候,应该是先找经纪人,告诉经纪人想让我写代码。
  • 十年过去了,我越来越红了,头发也越来越少。不是粉丝想要我写代码,我就写了。我要收费了。但是呢,作为一个公众人物,不可能是我自己说:我要收 10000 万,我才会去写代码。于是这就让经纪人对粉丝说:只有 10000 万,我才会写代码。
  • 无论外界是想要我干什么,都要经过我的经纪人。我的经纪人也会在其中考虑收费、推脱它们的请求。
  • 经纪人就是代理,实际写代码的还是我

所以说代理模式就是:当前对象不愿意干的,没法干的东西委托给别的对象来做,我只要做好本分的东西就好了!

二、用代码描述代理模式(静态代理)

这里有一个程序员接口,他们每天就是写代码

public interface Programmer {

    // 程序员每天都写代码
    void coding();

}

Java3y 也是一个程序员,他也写代码(每个程序员写的代码都不一样,所以分了接口和实现类)

public class Java3y implements Programmer {

    @Override
    public void coding() {
        System.out.println("Java3y最新文章:......给女朋友讲解什么是代理模式.......");
    }
}

此时 Java3y 已经是一个网红了,他不想枯燥地写代码。他在想:“在写代码时能赚钱就好咯,有人给我钱,我才写代码”。但是,Java3y 的文笔太烂了,一旦有什么冬瓜豆腐,分分钟变成过气网红,这是 Java3y 不愿意看到的。

而知乎、博客园这种平台又不能自己给自己点赞来吸引流量(--> 当前对象无法做)

所以 Java3y 去请了一个**程序员大 V(代理)**来实现自己的计划,这个程序员大 V 会每次让 Java3y 发文章时,就给 Java3y 点赞、评论、鼓吹这文章好。只要流量有了,钱就到手了。

public class ProgrammerBigV implements Programmer {

    // 指定程序员大V要让谁发文章(先发文章、后点赞)
    private Java3y java3y ;

    public ProgrammerBigV(Java3y java3y) {
        this.java3y = java3y;
    }

    // 程序员大V点赞评论收藏转发
    public void upvote() {
        System.out.println("程序员大V点赞评论收藏转发!");
    }

    @Override
    public void coding() {

        // 让Java3y发文章
        java3y.coding();

        // 程序员大V点赞评论收藏转发!
        upvote();
    }
}

文章(代码)还是由 Java3y 来发,但每次发送之后程序员大 V 都会点赞。

public class Main {

    public static void main(String[] args) {

        // 想要发达的Java3y
        Java3y java3y = new Java3y();

        // 受委托程序员大V
        Programmer programmer = new ProgrammerBigV(java3y);

        // 受委托程序员大V让Java3y发文章,大V(自己)来点赞
        programmer.coding();
    }  
}

942996074-5af034bb46090_articlex (725Ã127)

这样一来,不明真相的路人就觉得 Java3y 是真厉害,知识付费。

2.1 透明代理(普通代理)

经过一段时间,Java3y 尝到甜头了,觉得这是一条财路。于是 Java3y 给足了程序员大 V 钱,让程序员大 V 只做他的生意,不能做其他人的生意(断了其他人的财路)。

于是乎,程序员大 V 做 Java3y 一个人的生意:

public class ProgrammerBigV implements Programmer {

    // 指定程序员大V要给Java3y点赞
    private Java3y java3y ;

    // 只做Java3y的生意了
    public ProgrammerBigV() {
        this.java3y = new Java3y();
    }

    // 程序员大V点赞评论收藏转发
    public void upvote() {
        System.out.println("程序员大V点赞评论收藏转发!");
    }

    @Override
    public void coding() {

        // 让Java3y发文章了
        java3y.coding();

        // 程序员大V点赞评论收藏转发!
        upvote();

    }
}

于是乎,程序员大 V 想要赚点零花钱的时候直接让 Java3y 发文章就好了。

public class Main {

    public static void main(String[] args) {

        // 受委托程序员大V
        Programmer programmer = new ProgrammerBigV();

        // 受委托程序员大V让Java3y发文章,大V来点赞
        programmer.coding();

    }
}

此时,真实对象(Java3y)对外界来说是透明的

2.2 代理类自定义方法

程序员大 V 看到 Java3y 一直顺风顺水,赚大钱了。觉得是时候要加价了,于是在点赞完毕后就跟 Java3y 说每点完一次赞加 100 块

于是乎,程序员大 V 就增添了另外一个方法:addMoney()

public class ProgrammerBigV implements Programmer {


    // ..省略了上面的代码

    // 加价啦
    public void addMoney() {
        System.out.println("这次我要加100块");
    }

    @Override
    public void coding() {

        // 让Java3y发文章了
        java3y.coding();

        // 程序员大V点赞评论收藏转发!
        upvote();

        // 加价
        addMoney();

    }
}

于是乎程序员大 V 每次都能多 100 块:

2735280823-5af034badcd2a_articlex (774Ã491)

三、动态代理

几年时间过去了,Java3y 靠着程序员的大 V 点赞还是没发财(本质上 Java3y 还没有干货,没受到大众的认可)。此时已经有很多人晋升成了程序员大 V 了,但是之前的那个程序员大 V 还是一直累加着钱...虽然在开始的时候 Java3y 尝到了甜头,但现在 Java3y 财政已经匮乏了。

Java3y 将自己的失败认为:一定是那个程序员大 V 转门为我点赞被识破了,吃瓜群众都知道了,他收费又那么贵。

于是 Java3y 不请程序员大 V 了,请水军来点赞(水军便宜,只要能点赞就行了):

public class Main {

    public static void main(String[] args1) {

        // Java3y请水军
        Java3y java3y = new Java3y();

        Programmer programmerWaterArmy = (Programmer) Proxy.newProxyInstance(java3y.getClass().getClassLoader(), java3y.getClass().getInterfaces(), (proxy, method, args) -> {

            // 如果是调用coding方法,那么水军就要点赞了
            if (method.getName().equals("coding")) {
                method.invoke(java3y, args);
                System.out.println("我是水军,我来点赞了!");

            } else {
                // 如果不是调用coding方法,那么调用原对象的方法
                return method.invoke(java3y, args);
            }

            return null;
        });

        // 每当Java3y写完文章,水军都会点赞
        programmerWaterArmy.coding();

    }

}

每当 Java3y 发文章的时候,水军都会点赞。

3230573707-5af034bad5a85_articlex (748Ã168)

Java3y 感叹:请水军真是方便啊~

3.1 动态代理调用过程

我们来看看究竟是怎么请水军的:

Java 提供了一个 Proxy 类,调用它的 newInstance 方法可以生成某个对象的代理对象,该方法需要三个参数:

null

  • 参数一:生成代理对象使用哪个类装载器【一般我们使用的是被代理类的装载器】
  • 参数二:生成哪个对象的代理对象,通过接口指定【指定要被代理类的接口】
  • 参数三:生成的代理对象的方法里干什么事【实现 handler 接口,我们想怎么实现就怎么实现】

在编写动态代理之前,要明确几个概念:

  • 代理对象拥有目标对象相同的方法【因为参数二指定了对象的接口,代理对象会实现接口的所有方法】
  • 用户调用代理对象的什么方法,都是在调用处理器的 invoke 方法。【被拦截】
  • 使用 JDK 动态代理必须要有接口【参数二需要接口】

上面也说了:代理对象会实现接口的所有方法,这些实现的方法交由我们的 handler 来处理!

  • 所有通过动态代理实现的方法全部通过 invoke() 调用

null

所以动态代理调用过程是这样子的:

null

3.2 静态代理和动态代理的区别

很明显的是:

  • 静态代理需要自己写代理类--> 代理类需要实现与目标对象相同的接口
  • 而动态代理不需要自己编写代理类--->(是动态生成的)

使用静态代理时:

  • 如果目标对象的接口有很多方法的话,那我们还是得一一实现,这样就会比较麻烦

使用动态代理时:

  • 代理对象的生成,是利用 JDKAPI,动态地在内存中构建代理对象(需要我们指定创建 代理对象/目标对象 实现的接口的类型),并且会默认实现接口的全部方法

四、典型应用

我们之前写中文过滤器的时候,需要使用包装设计模式来设计一个 request 类。如果不是 Servlet 提供了实现类给我们,我们使用包装设计模式会比较麻烦

现在我们学习了动态代理了,动态代理就是拦截直接访问对象,可以给对象进行增强的一项技能

4.1 中文过滤器

public void doFilter(final ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        final HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        response.setContentType("text/html;charset=UTF-8");
        request.setCharacterEncoding("UTF-8");


        //放出去的是代理对象
        chain.doFilter((ServletRequest) Proxy.newProxyInstance(CharacterEncodingFilter.class.getClassLoader(), request.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                //判断是不是getParameter方法
                if (!method.getName().equals("getParameter")) {

                    //不是就使用request调用
                   return method.invoke(request, args);
                }

                //判断是否是get类型的
                if (!request.getMethod().equalsIgnoreCase("get")) {
                   return method.invoke(request, args);
                }

                //执行到这里,只能是get类型的getParameter方法了。
                String value = (String) method.invoke(request, args);
                if (value == null) {
                    return null;
                }
                return new String(value.getBytes("ISO8859-1"), "UTF-8");
            }

        }), response);

    }

五、总结

第一次以这种方式写文章,举的例子可能会不妥,希望大家见谅~

本文主要讲解了代理模式的几个要点,其实还有一些细节的:比如“强制代理”(只能通过被代理对象找到代理对象,不能绕过代理对象直接访问被代理对象)。只是用得比较少,我就不说了~~

要实现动态代理必须要有接口的,动态代理是基于接口来代理的(实现接口的所有方法),如果没有接口的话我们可以考虑 cglib 代理。

cglib 代理也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能

这里我就不再贴出代码来了,因为 cglib 的代理教程也很多,与动态代理实现差不多~~~~~

总的来说:代理模式是我们写代码中用得很多的一种模式了,Spring 的 AOP 底层其实就是动态代理来实现的--> 面向切面编程。

其实只要记住一点:原有的对象需要额外的功能,想想动态代理这项技术
原文地址:https://segmentfault.com/a/1190000014764125

  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    200 引用 • 120 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...