掘金 后端 ( ) • 2021-06-12 14:26
.markdown-body{color:#595959;font-size:15px;font-family:-apple-system,system-ui,BlinkMacSystemFont,Helvetica Neue,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Arial,sans-serif;background-image:linear-gradient(90deg,rgba(60,10,30,.04) 3%,transparent 0),linear-gradient(1turn,rgba(60,10,30,.04) 3%,transparent 0);background-size:20px 20px;background-position:50%}.markdown-body p{color:#595959;font-size:15px;line-height:2;font-weight:400}.markdown-body p+p{margin-top:16px}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{padding:30px 0;margin:0;color:#135ce0}.markdown-body h1{position:relative;text-align:center;font-size:22px;margin:50px 0}.markdown-body h1:before{position:absolute;content:"";top:-10px;left:50%;width:32px;height:32px;transform:translateX(-50%);background-size:100% 100%;opacity:.36;background-repeat:no-repeat;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABfVBMVEX///8Ad/8AgP8AgP8AgP8Aff8AgP8Af/8AgP8AVf8Af/8Af/8AgP8AgP8Af/8Afv8AAP8Afv8Afv8Aef8AgP8AdP8Afv8AgP8AgP8Acf8Ae/8AgP8Af/8AgP8Af/8Af/8AfP8Afv8AgP8Af/8Af/8Afv8Afv8AgP8Afv8AgP8Af/8Af/8AgP8AgP8Afv8AgP8Af/8AgP8AgP8AgP8Ae/8Afv8Af/8AgP8Af/8AgP8Af/8Af/8Aff8Af/8Abf8AgP8Af/8AgP8Af/8Af/8Afv8AgP8AgP8Afv8Afv8AgP8Af/8Aff8AgP8Afv8AgP8Aff8AgP8AfP8AgP8Ae/8AgP8Af/8AgP8AgP8AgP8Afv8AgP8AgP8AgP8Afv8AgP8AgP8AgP8AgP8AgP8Af/8AgP8Af/8Af/8Aev8Af/8AgP8Aff8Afv8AgP8AgP8AgP8Af/8AgP8Af/8Af/8AgP8Afv8AgP8AgP8AgP8AgP8Af/8AeP8Af/8Af/8Af//////rzEHnAAAAfXRSTlMAD7CCAivatxIDx5EMrP19AXdLEwgLR+6iCR/M0yLRzyFF7JupSXn8cw6v60Q0QeqzKtgeG237HMne850/6Qeq7QaZ+WdydHtj+OM3qENCMRYl1B3K2U7wnlWE/mhlirjkODa9FN/BF7/iNV/2kASNZpX1Wlf03C4stRGxgUPclqoAAAABYktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEaBzgZ4yeM3AAAAT9JREFUOMvNUldbwkAQvCAqsSBoABE7asSOBRUVVBQNNuy9996789+9cMFAMHnVebmdm+/bmdtbQv4dOFOW2UjPzgFyLfo6nweKfIMOBYWwFtmMPGz2Yj2pJI0JDq3udJW6VVbmKa9I192VQFV1ktXUAl5NB0cd4KpnORqsEO2ZIRpF9gJfE9Dckqq0KuZt7UAH5+8EPF3spjsRpCeQNO/tA/qDwIDA+OCQbBoKA8NOdjMySgcZGVM6jwcgRuUiSs0nlPFNSrEpJfU0jTLD6llqbvKxei7OzvkFNQohi0vAsj81+MoqsCaoPOQFgus/1LyxichW+hS2JWCHZ7VlF9jb187pIAYcHiViHAMnp5mTjJ8B5xeEXF4B1ze/fTh/C0h398DDI9HB07O8ci+vRBdvdGnfP4gBuM8vw7X/G3wDmFhFZEdxzjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTgtMDEtMjZUMDc6NTY6MjUrMDE6MDA67pVWAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE4LTAxLTI2VDA3OjU2OjI1KzAxOjAwS7Mt6gAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAWdEVYdFRpdGxlAGp1ZWppbl9sb2dvIGNvcHlxapmKAAAAV3pUWHRSYXcgcHJvZmlsZSB0eXBlIGlwdGMAAHic4/IMCHFWKCjKT8vMSeVSAAMjCy5jCxMjE0uTFAMTIESANMNkAyOzVCDL2NTIxMzEHMQHy4BIoEouAOoXEXTyQjWVAAAAAElFTkSuQmCC)}.markdown-body h2{position:relative;font-size:20px;border-left:4px solid;padding:0 0 0 10px;margin:30px 0}.markdown-body h3{font-size:16px}.markdown-body ul{list-style:disc outside;margin-left:2em;margin-top:1em}.markdown-body li{line-height:2;color:#595959}.markdown-body img.loaded{margin:0 auto;display:block}.markdown-body blockquote{background:#fff9f9;margin:2em 0;padding:2px 20px;border-left:4px solid #b2aec5}.markdown-body blockquote p{color:#666;line-height:2}.markdown-body a{color:#036aca;border-bottom:1px solid rgba(3,106,202,.8);font-weight:400;text-decoration:none}.markdown-body em strong,.markdown-body strong{color:#036aca}.markdown-body hr{border-top:1px solid #135ce0}.markdown-body pre{overflow:auto}.markdown-body code,.markdown-body pre{overflow:auto;position:relative;line-height:1.75;font-family:Menlo,Monaco,Consolas,Courier New,monospace}.markdown-body pre>code{font-size:12px;padding:15px 12px;margin:0;word-break:normal;display:block;overflow-x:auto;color:#333;background:#f8f8f8}.markdown-body code{word-break:break-word;border-radius:2px;overflow-x:auto;background-color:#fff5f5;color:#ff502c;font-size:.87em;padding:.065em .4em}.markdown-body table{border-collapse:collapse;margin:1rem 0;overflow-x:auto}.markdown-body table td,.markdown-body table th{border:1px solid #dfe2e5;padding:.6em 1em}.markdown-body table tr{border-top:1px solid #dfe2e5}.markdown-body table tr:nth-child(2n){background-color:#f6f8fa}

这是我参与更文挑战的第12天,活动详情查看: 更文挑战

微信公众号:潇雷

当努力到一定程度,幸运自与你不期而遇。

前言

创建者模式的五种已经讲解完毕。今日开始进入结构型模式的学习。

那么什么是结构型模式?这个和创建型模式的用途不一样,就像我们的数据结构,分为线性表、链表、栈、树、图等结构。更多的是通过自身组成的一系列结构来处理不同的数据。而结构型模式也是如此,它描述如何将类或对象按某种布局组成更大的结构。

它分为类结构型模式和对象结构型模式。

  • 类继承结构的就只能通过继承机制组织接口和类
  • 而对象结构型可以共同组合成多个对象,比较灵活

结构性模式分为以下7种:

  • 代理模式
  • 适配器模式
  • 装饰者模式
  • 桥接模式
  • 外观模式
  • 组合模式
  • 享元模式

一、入门代理模式

1.1 什么叫代理模式?

代理模式(Proxy Patten)是一种常见的设计模式

所谓的代理者是指一个类别可以作为其它东西的接口。代理者可以作任何东西的接口:网上连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。

简单说呢,代理者B 实现了A 的功能,并将功能交给A去做,同时,在加上自己的功能。

例如,媒婆给男女介绍对象,只不过是个牵线的作用,后续成不成还得看男女两人,同时加上自己的功能:收取一些费用。

image-20210612101227978

1.2 为什么会出现代理模式?

还是从上面的例子出发,生活中,我们确实需要很多这种“代理类“的角色,就像媒人、中介、经纪人等的中间角色,那出现他们的原因很显然,就是有这需求存在,我们很多时候并不能直接找到另一半,直接找到房子。通过媒人的介绍,她会做些”添油加醋的”事情,从而撮合你们在一起。

总结代码里面就是:我们使用代理对象来代替对真实对象的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。这也就是代理模式的出现原因,扩展目标对象的功能,把一个精神小伙塑造成“男神”。

1.3 使用场景?

  • 远程(Remote)代理

    本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。

  • 防火墙(Firewall)代理

    当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。

  • 保护(Protect or Access)代理

    控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。

代理模式的应用场景还是很多的,虽然它在一定程度上增加了系统的复杂度。

但是它却有着下面几个优点:

  • 代理模式在访问对象和目标对象之间起着中介的作用
  • 代理模式可以扩展目标对象的功能
  • 代理模式将分割访问者和目标对象,一定程度上降低了系统的耦合度。

二、Java代理模式

2.1 分类

从上面例子可知,代理对象游离于目标对象和访问对象之间的中介。在Java中它可以按照代理类生成时机不同分为静态代理和动态代理。

  • 静态代理:在编译器就就生成
  • 动态代理:在Java运行时动态生成
    • JDK代理
    • CGLib代理

2.2 角色

代理模式分为三种角色:

  • 抽象角色:通过接口或抽象类 声明真实主题和代理实现的业务方法
  • 真实角色:实现了抽象角色中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理角色:提供了与真实角色相同的接口,其内部含有对真实角色的引用,可以对真实角色方法做增强。

2.3 静态代理

这里我们用静态代理来实现下上面的案例。

对应上面举的案例。

  • 抽象角色中的方法可以定义为 “找对象。
  • 真实角色中实现了找对象的方法。例如定义自己的性格、择偶标准等
  • 代理角色实现了真实角色中的方法,并对它进行了扩展,进行美化、并收取费用。

代码如下:

// 1、定义一个找对象的接口,实现一个找男朋友的方法
public interface FindFriend {
    void findBoyFriend();
}

// 2、真实对象,耿直boy说话
public class Boy implements FindFriend{
    @Override
    public void findBoyFriend() {
        System.out.println("可以作为你的男朋友吗?");
    }
}

// 3、媒人做了啥?
public class ProxyMatchmaker implements FindFriend{
    private Boy boy =new Boy();
    @Override
    public void findBoyFriend() {
        System.out.println("小伙子头脑精神\n" +
                "会说话,会过日子\n" +
                "潜力股,贼精神!");
        boy.findBoyFriend();
        System.out.println("小伙子,收你500大洋!事成后,再收你500,嘿嘿~");
    }
}

// 4、测试,有人来婚介找男朋友
public class StaticProxyTest {
    public static void main(String[] args) {
        ProxyMatchmaker proxyMatchmaker = new ProxyMatchmaker();
        proxyMatchmaker.findBoyFriend();
    }
}
复制代码

输出 结果:

小伙子头脑精神
会说话,会过日子
潜力股,贼精神!
可以作为你的男朋友吗?
小伙子,收你500大洋!事成后,再收你500,嘿嘿~
复制代码

静态代理的特点:

  • 代理类是自己手工实现的,自己创建一个Java类,表示代理类
  • 同时,你所要代理的目标类是确定的

缺点就是,访问类和目标类增加了,出现了几百人耿直boy和找对象的girl,会影响众多的实现类和代理类。

2.4 JDK动态代理

JDK提供了针对静态代理缺陷的标准解决方案,称为动态代理。Java 提供了一个动态代理类Proxy,它提供了一个创建代理对象的静态方法来获取代理对象。

// 1、定义一个找对象的接口,实现一个找男朋友的方法
public interface FindFriend {
    void findBoyFriend();
}

// 2、真实对象,耿直boy说话
public class BoyZ implements FindFriend{
    @Override
    public void findBoyFriend() {
        System.out.println("张三可以做你男朋友吗?");
    }
}

// 3、媒人说了啥?
public class ProxyFactory {
    private BoyZ boyZ =new BoyZ();

    public  FindFriend getProxyObject(){
     //使用Proxy获取代理对象
        /*
            newProxyInstance()方法参数说明:
                ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可
                Class>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口
                InvocationHandler h : 代理对象的调用处理程序
         */
        FindFriend o = (FindFriend) Proxy.newProxyInstance(
        boyZ.getClass().getClassLoader(),
                boyZ.getClass().getInterfaces(),
                new InvocationHandler() {
                /*
                        InvocationHandler中invoke方法参数说明:
                            proxy : 代理对象
                            method : 对应于在代理对象上调用的接口方法的 Method 实例
                            args : 代理对象调用接口方法时传递的实际参数
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("小伙子头脑精神\n" +
                                "会说话,会过日子\n" +
                                "潜力股,贼精神!");
                         //执行真实对象
                        Object result = method.invoke(boyZ, args);
                        System.out.println("小伙子,收你500大洋!事成后,再收你500,嘿嘿~");
                        return result;
                    }
                });
        return o;
    }
}

// 4、测试
public class StaticProxyTest {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        FindFriend proxyObject = proxyFactory.getProxyObject();
        proxyObject.findBoyFriend();
    }
}
复制代码

打印结果:

小伙子头脑精神
会说话,会过日子
潜力股,贼精神!
张三可以做你男朋友吗?
小伙子,收你500大洋!事成后,再收你500,嘿嘿~
复制代码

通过JDK来实现动态代理,想想ProxyFactory 是代理类吗?

并不是,它是一个帮我们找到代理类的类。真正的代理类是在程序运行时在内存生成的类。也就是在getProxyObject()方法中生成的。

通过 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构:

public final class $Proxy0 extends Proxy implements FindFriend {
    private static Method m3;

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    public final void findBoyFriend() {
       this.h.invoke(this, m3, null);
    }

    static {
       m3 = Class.forName("patten.proxy.FindFriend").getMethod("findBoyFriend", new Class[0]);
       return;
}

//Java提供的动态代理相关类
public class Proxy implements java.io.Serializable {
    protected InvocationHandler h;
     
    protected Proxy(InvocationHandler h) {
        this.h = h;
    }
}

复制代码

也就是说真正的代理类 是Proxy的子类,同时继承了抽象角色的方法,也符合我们之前对代理角色的定义。通过传入真实角色的方法,找到代理角色的方法,进行多态输出。

然后代理类角色的findBoyFriend()方法调用了InvocationHandler的invoke方法。通过反射真正实现真实对象的shell()方法。

2.5 CGLibg动态代理

在上面的定义中,如果没有找对象的这个接口,就只定义了一个耿直男孩。这种情况JDK动态代理就无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。

CGLib 是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理起到一个很好的补充.

首先引入jar包:


    cglib
    cglib
    2.2.2

复制代码

代码如下:

// 1、定义真实对象
public class BoyZ  {
    public void findBoyFriend() {
        System.out.println("张三可以做你男朋友吗?");
    }
}
// 2、实现代理功能
public class ProxyFactory implements MethodInterceptor {
/**
     * 指定目标对象
     */
    private BoyZ boyZ =new BoyZ();

    public BoyZ getProxyObject(){
    //1.工具类
        Enhancer enhancer = new Enhancer();
        //2.设置父类
        enhancer.setSuperclass(boyZ.getClass());
        //3.设置回调函数
        enhancer.setCallback(this);
        //4.创建子类(代理对象)
        BoyZ obj=(BoyZ)enhancer.create();
        return obj;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("小伙子头脑精神\n" +
                "会说话,会过日子\n" +
                "潜力股,贼精神!");
        BoyZ o1 = (BoyZ) methodProxy.invokeSuper(o, objects);
        return o1;
    }
}
// 3、测试
public class Test {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory();
        BoyZ proxyObject = proxyFactory.getProxyObject();
        proxyObject.findBoyFriend();
    }
}
复制代码

GGLib是针对类实现处理的,通过 生成目标类的子类,然后覆盖其中的方法。因此,目标类不能是final修饰,方法不能被static/final修饰。

2.6 三种模式对比

2.6.1jdk代理和CGLIB代理
  • 使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。

  • 在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。

2.6.2 动态代理和静态代理
  • 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

  • 如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题

三、小结

本文对代理模式进行了初步讲解,在理解代理模式的入门和使用后,虽然实际开发中应用的代理模式的场景很少,但它的理念却深深的烙在了很多框架的源码里面,比如CGLIb和spring aop。代理模式的思想就是如此。这就是结构性模式的开篇。后续将继续我们的浅谈设计模式之旅,创作不易,不妨点个赞和关注。