zoukankan      html  css  js  c++  java
  • 代理模式详解(静态代理和动态代理的区别以及联系)

    原文链接:https://www.cnblogs.com/takumicx/p/9285230.html

    1. 前言

    代理模式可以说是生活中处处可见。比如说在携程上定火车票,携程在这里就起到了一个代理的作用,比起我们在官网上或者直接去柜台订票,携程可以为用户提供更多人性化的选择。再比如代购,我自己的mbp就是委托别人从香港买回来的,那么那个代购人就相当于代理,免去了我来回的车费以及办签证的麻烦。直观的理解,代理是这么一个对象,我们可以把工作委托给它,由它帮我们去执行工作同时解决工作相关的各种麻烦,最后把工作成果交给我们。这样一来我们只需要关注问题的核心,并告知代理,而不需要为其他琐碎的细节操心,这正是代理模式最大的好处,让客户端专注于真正的事务处理。具体而言,代理模式分为静态代理和动态代理,它们的设计思想类似,实现却大相径庭。它们的实现方式以及它们的区别是面试时经常会被问到的。下面我们就来详细介绍它们。

    2. 代理模式详解

    2.1 定义

    为另一个对象提供一个替身或占位符以控制对这个对象的访问
    定义简单明了。但还是有些没有解释清楚的地方,控制对对象的访问是为了干什么?其实是为了对真实的业务方法作某种形式的增强,比如在业务方法调用前作前置处理,在方法调用后作后置处理,而这些对客户端都是透明的。

    2.2 普通代理模式类结构

    被代理类和代理类实现同一接口,根据面向接口编程原则,可以使用被代理类的地方,统统可以用代理类代替,同时类内部持有被代理类的引用,真正的业务处理逻辑可以交给被代理类去作。

    2.3 普通代理模式的实现(静态代理)

    假设我要写一个日志代理,在真实的业务方法调用前后各记一条日志,看看静态代理是怎么做的。

    • 业务接口
    public interface IService {
    
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">service1</span><span class="hljs-params">()</span></span>;
    
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">service2</span><span class="hljs-params">()</span></span>;
    

    }

    接口含有两个抽象业务方法

    • 被代理类
    public class RealService implements IService {
    
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">service1</span><span class="hljs-params">()</span> </span>{
        System.out.println(<span class="hljs-string">"service1"</span>);
    }
    
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">service2</span><span class="hljs-params">()</span> </span>{
        System.out.println(<span class="hljs-string">"service2"</span>);
    }
    

    }

    被代理类实现业务接口,并且重写了业务方法。

    • 日志代理
    public class StaticLogProxy implements IService {
    
    <span class="hljs-keyword">private</span> IService iService;
    
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">StaticLogProxy</span><span class="hljs-params">(IService iService)</span> </span>{
        <span class="hljs-keyword">this</span>.iService = iService;
    }
    
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">service1</span><span class="hljs-params">()</span> </span>{
        System.out.println(<span class="hljs-string">"service1 start!"</span>);
        iService.service1();
        System.out.println(<span class="hljs-string">"service1 end!"</span>);
    
    }
    
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">service2</span><span class="hljs-params">()</span> </span>{
        System.out.println(<span class="hljs-string">"service2 start!"</span>);
        iService.service2();
        System.out.println(<span class="hljs-string">"service2 end!"</span>);
    }
    

    }

    • 运行结果

    如上图所示,在业务方法执行前后分别记了一条日志,表示业务方法执行的开始和结束。我们的代理类成功对原有业务方法做了增强。但是静态代理存在以下问题:
    1.代理类和被代理类耦合,适用性差。试想如果我希望为所有的业务类添加日志增强逻辑,那么岂不是要为几乎每个业务类编写代理类?这是不现实也是开发时无法接受的。
    2.代理类的增强逻辑和业务逻辑过于耦合,不利于后期维护和扩展。从service1和service2中就可以看出,除了中间的业务处理不一样,代理类的处理逻辑是一样的,而我们竟然没有将其分离。
    以上两点其实反映的是同一个问题:我们希望自己编写的代理类对所有业务类,所有业务方法都适用,而静态代理的泛用性太差了。问题的关键在于编写代理逻辑和业务逻辑分离的代理类,运行时才将其和具体的业务类绑定,对其业务方法做增强。为此,我们需要动态代理。动态代理的实现方式很多,下面以jdk自带的代理方式做说明。

    3. JDK动态代理详解

    3.1 JDK动态代理实现

    • 方法调用处理器
    public class LogHandler implements InvocationHandler {
    
    <span class="hljs-comment">//被代理对象</span>
    <span class="hljs-keyword">private</span> Object target;
    
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">LogHandler</span><span class="hljs-params">(Object target)</span> </span>{
        <span class="hljs-keyword">this</span>.target = target;
    }
    
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">invoke</span><span class="hljs-params">(Object proxy, Method method, Object[] args)</span> <span class="hljs-keyword">throws</span> Throwable </span>{
        System.out.println(method.getName() + <span class="hljs-string">" start!"</span>);<span class="hljs-comment">//前置处理</span>
        Object res = method.invoke(<span class="hljs-keyword">this</span>.target, args);<span class="hljs-comment">//执行业务方法</span>
        System.out.println(method.getName() + <span class="hljs-string">" end!"</span>);<span class="hljs-comment">//后置处理</span>
        <span class="hljs-keyword">return</span> res;
    }
    

    }

    每一个代理对象都有一个与之关联的方法调用处理器,该处理器实现了InvocationHandler接口并重写了invoke方法。当我们调用代理对象的方法的时候,对该方法的调用会转交给方法调用处理器的invoke方法来执行,所以方法调用处理器的invoke方法是动态代理的核心。该方法内是通用的代理逻辑。在我们通过反射的方式通过被代理对象target执行业务逻辑的前后,可以对其作前置和后置增强。

    • 客户端代码
    public class Client {
    
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(<span class="hljs-params">String[] args</span>) </span>{
    
        <span class="hljs-comment">//1.创建被代理对象</span>
        RealService realService = <span class="hljs-keyword">new</span> RealService();
    
        <span class="hljs-comment">//2.创建动态代理的方法调用处理器</span>
        LogHandler logHandler = <span class="hljs-keyword">new</span> LogHandler(realService);
    
        <span class="hljs-comment">//3.创建动态代理对象</span>
        IService service=(IService)Proxy.newProxyInstance(logHandler.getClass().getClassLoader(),
                realService.getClass().getInterfaces(),logHandler);
    
        service.service1();
        System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"---------------"</span>);
        service.service2();
    
    }
    

    }

    第一步我们创建了被代理对象realService;第二步我们创建了动态代理的核心:方法调用处理器。因为处理器内部需要委托被代理对象去执行真正的业务方法,所以需要传入被代理对象作参数。第三步我们通过调用反射包下的Proxy类的静态方法去生成真正的代理对象。该方法的方法签名如下

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

    该方法含有三个参数
    ClassLoader loader:指定加载的代理类的类加载器
    Class<?>[] interfaces:指定代理类要实现的接口
    InvocationHandler h:指定方法调用处理器
    关于第二个参数可能要说明下,因为jdk的动态代理是针对接口的动态代理,代理类需要实现指定的业务接口,这里就是被代理类实现的那些接口。这样就能充分利用java多态特性,代码中所有能使用被代理对象的地方都能用代理对象进行替换。

    • 运行结果

    和静态代理的运行结果一样。如果我们要对其他业务类使用日志代理,只需要修改下客户端代码就行,这是静态代理办不到的。具有良好的扩展性是动态代理相比静态代理最大的优势。

    3.2 方法调用流程图

    • 1.客户端调用代理对象的业务方法,创建代理对象的时候传入了接口数组参数,故而代理对象也实现了业务接口。
    • 2.代理对象将请求转发给方法调用处理器的invoke方法
    • 3.方法调用处理器在invoke方法内部通过反射的方式调用被代理对象的业务方法

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

    • 静态代理编译期生成代理类;动态代理运行期生成代理类。
    • 静态代理和被代理类及其业务逻辑耦合,适用性较差且代理逻辑难以扩展;动态代理可以在不知道被代理类的前提下编写代理逻辑,运行时才决定被代理对象,适用性好且代理逻辑易于扩展。

    3.4 其他实现动态代理的方式

    • cglib面向类的动态代理
    • javaassist字节码操作库实现
    • asm

    4. 总结

    代理用以控制对对象的访问,本质上是对其功能提供某种形式的增强。按实现又可分为静态代理和动态代理。动态代理因其代理逻辑和业务逻辑相分离的特点,具有良好的适用性和可扩展性,是Spring中AOP的底层实现。

  • 相关阅读:
    createpipe
    JavaScript中的JSON(非原创一)
    Delphi资源文件的应用(转)
    PowerDesigner15对SQL2005反向工程问题.
    readonly(C# 参考)
    SQL SERVER 2005 数据挖掘与商业智能完全解决方案学习笔记(四)
    什么是Actor?
    ORACLE自增长字段实现(转)
    SQL SERVER 2005 数据挖掘与商业智能完全解决方案学习笔记(二)
    J2EE框架(TomcatStrutsHibernateSpringAjax(dojo))开发步骤:
  • 原文地址:https://www.cnblogs.com/LoveShare/p/10796966.html
Copyright © 2011-2022 走看看