掘金 后端 ( ) • 2021-05-26 19:12
.markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:15px;overflow-x:hidden;color:#333}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{line-height:1.5;margin-top:35px;margin-bottom:10px;padding-bottom:5px}.markdown-body h1{font-size:30px;margin-bottom:5px}.markdown-body h2{padding-bottom:12px;font-size:24px;border-bottom:1px solid #ececec}.markdown-body h3{font-size:18px;padding-bottom:0}.markdown-body h4{font-size:16px}.markdown-body h5{font-size:15px}.markdown-body h6{margin-top:5px}.markdown-body p{line-height:inherit;margin-top:22px;margin-bottom:22px}.markdown-body img{max-width:100%}.markdown-body hr{border:none;border-top:1px solid #ddd;margin-top:32px;margin-bottom:32px}.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 code,.markdown-body pre{font-family:Menlo,Monaco,Consolas,Courier New,monospace}.markdown-body pre{overflow:auto;position:relative;line-height:1.75}.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 a{text-decoration:none;color:#0269c8;border-bottom:1px solid #d1e9ff}.markdown-body a:active,.markdown-body a:hover{color:#275b8c}.markdown-body table{display:inline-block!important;font-size:12px;width:auto;max-width:100%;overflow:auto;border:1px solid #f6f6f6}.markdown-body thead{background:#f6f6f6;color:#000;text-align:left}.markdown-body tr:nth-child(2n){background-color:#fcfcfc}.markdown-body td,.markdown-body th{padding:12px 7px;line-height:24px}.markdown-body td{min-width:120px}.markdown-body blockquote{color:#666;padding:1px 23px;margin:22px 0;border-left:4px solid #cbcbcb;background-color:#f8f8f8}.markdown-body blockquote:after{display:block;content:""}.markdown-body blockquote>p{margin:10px 0}.markdown-body ol,.markdown-body ul{padding-left:28px}.markdown-body ol li,.markdown-body ul li{margin-bottom:0;list-style:inherit}.markdown-body ol li .task-list-item,.markdown-body ul li .task-list-item{list-style:none}.markdown-body ol li .task-list-item ol,.markdown-body ol li .task-list-item ul,.markdown-body ul li .task-list-item ol,.markdown-body ul li .task-list-item ul{margin-top:0}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:3px}.markdown-body ol li{padding-left:6px}.markdown-body .contains-task-list{padding-left:0}.markdown-body .task-list-item{list-style:none}@media (max-width:720px){.markdown-body h1{font-size:24px}.markdown-body h2{font-size:20px}.markdown-body h3{font-size:18px}}

我们在进行代码开发的时候经常会听到这样一句话不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。这里面就提到了代理,代理模式是在设计模式中应用最广泛的一种。我们平时在开发的时候也会经常使用。接下来我们就研究一下代理模式以及相关的知识。

什么是代理模式

代理模式在整个设计模式中属于结构类模式,除了代理模式以外还包括我们了解过的适配器模式,装饰模式,组合模式等。他们都是通过组合类或者对象来进一步产生更复杂的结构和逻辑。在java中我们经常使用spring框架进行开发,spring中的AOP的核心就是代理。 代理模式配图

代理模式中涉及几个角色:

  • 抽象主题角色:定义了被代理角色和代理角色的共同接口或者抽象类。
  • 被代理角色:实现或者继承抽象主题角色,定义实现具体业务逻辑的实现。
  • 代理角色:实现或者继承抽象主题角色,持有被代理角色的引用,控制和限制被代理角色的实现,并且拥有自己的处理方法(预处理和善后)
静态代理

如上图我们用代码举个例子:

  1. 定义一个演员抽象接口类
/**
 * 演员抽象接口类
 */
public interface Iactor {
    //演员演戏
    public void action();
    //演员安排档期
    public void scheduleDay();
}

复制代码
  1. 超级明星类
/**
 * 超级明星类
 */
public class SuperStar implements Iactor {

    private String name;

    public SuperStar(String name) {
        this.name = name;
    }

    @Override
    public void action() {
        System.out.println(name+ "在演戏呢,快来看");
    }

    @Override
    public void scheduleDay() {
        System.out.println(name+ "安排档期");
    }
}

复制代码
  1. 经纪人类
/**
 * 经纪人类
 */
public class ProxyStar implements Iactor {

    private Iactor iactor;

    public ProxyStar(Iactor iactor) {
        this.iactor = iactor;
    }

    @Override
    public void action() {
        System.out.println("经纪人去叫演员了");
        iactor.action();
        System.out.println("经纪人把演员送回去了");
    }

    @Override
    public void scheduleDay() {
        System.out.println("经纪人安排了时间给演员确定");
        iactor.scheduleDay();
        System.out.println("经纪人对外公布时间安排");
    }
}
复制代码
  1. 客户端
public class Client {
    public static void main(String[] args) {
        //定义一个超级明星
        Iactor iactor = new SuperStar("jack");
        //定义一个经纪人 --代理了jack超级明星
        ProxyStar proxyStar = new ProxyStar(iactor);
        proxyStar.action();
        proxyStar.scheduleDay();
    }

}
复制代码

运行代码结果:

经纪人去叫演员了

jack在演戏呢,快来看

经纪人把演员送回去了

经纪人安排了时间给演员确定

jack安排档期

经纪人对外公布时间安排

以上代码在客户端通过ProxyStar得到指定代理角色,由代理来控制SuperStar对象。 代理模式的实现都是必须经过代理才能访问被代理类。就比如明星拍电影,不会跳过经纪人直接找到明星,而是经过经纪人再到明星,要不然就没有经纪人这个职位了。

上边我们介绍的是代理中的静态代理,静态代理模式有一些弊端,比如有其他的超级明星也需要这个经纪人去代理,不同的超级明星会的东西不一样,比如有的人会唱歌不会演戏,有的人会说相声不会唱歌。这个时候如果还是采用静态代理就会增加代码的复杂度,需要建立很多个代理类。

动态代理

根据如上的介绍,你会发现每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类

所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理。在Java中要想实现动态代理机制,需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy 类的支持。 我们举个例子,需求是需要在操作数据库的新增数据方法前和后打印输出日志。

  1. 操作数据库的实现类
public class UserManagerImpl implements UserManager {  
  
    @Override  
    public void addUser(String userId, String userName) {  
        System.out.println("UserManagerImpl.addUser");  
    }  
}  
复制代码

如上代码我们需要在addUser这个方法执行前和后输出打印日志。 2. 动态代理类

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

public class LogProxy implements InvocationHandler {
    // 目标对象
    private Object targetObject;
    //绑定关系,也就是关联到哪个接口(与具体的实现类绑定)的哪些方法将被调用时,执行invoke方法。
    public Object newProxyInstance(Object targetObject){
        this.targetObject=targetObject;
        //该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
        //第一个参数指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
        //第二个参数要实现和目标对象一样的接口,所以只需要拿到目标对象的实现接口
        //第三个参数表明这些被拦截的方法在被拦截时需要执行哪个InvocationHandler的invoke方法
        //根据传入的目标返回一个代理对象
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),
                targetObject.getClass().getInterfaces(),this);
    }
    @Override
    //关联的这个实现类的方法被调用时将被执行
    /*InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法,args表示方法的参数*/
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object ret=null;
        try{
            /*原对象方法调用前处理日志信息*/
            System.out.println("开始调用");

            //调用目标方法
            ret=method.invoke(targetObject, args);
            /*原对象方法调用后处理日志信息*/
            System.out.println("调用结束");
        }catch(Exception e){
            e.printStackTrace();
            throw e;
        }
        return ret;
    }
}

复制代码
  1. 动态调用
LogProxy logProxy=new LogProxy();
UserManager userManager=(UserManager)logProxy.newProxyInstance(new UserManagerImpl());
userManager.addUser("1", "三");
复制代码

如果需要代理其它对象则传进其它的实现类即可,比如传入

CompanyManager companyManager=(CompanyManager)logProxy.newProxyInstance(new CompanyManagerImpl());
复制代码

动态代理以上逻辑也就是springAOP的核心原理。

与装饰模式的区别

以上代理模式我们学习了之后在简单了解一下装饰模式,二者极为相似,对于代码实现来讲甚至可以是一模一样。我们简单迅速的写个装饰模式的例子:

//装饰器模式
public class Decorator implements Component{
        private Component component;
        public Decorator(Component component){
            this.component = component
        }
       public void operation(){
            //需要装饰的逻辑
            component.operation();
       }
}
复制代码
//装饰器的客户
public class Client{
        public static void main(String[] args){
            //客户指定了装饰者需要装饰的是哪一个类
            Component component = new Decorator(new ConcreteComponent());
           
        }
}
复制代码

我们平时开发在哪会看见大量的装饰模式呢,很简单就是在流相关操作上,各种inputstream相关类。仔细看一下是不是跟代理模式很像。 来个稀里糊涂的解释:装饰模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。代理模式中代理类可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个真实对象的实例。并且,当我们使用装饰模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。 其实上边这段话仔细理解也好理解。主要差别就是在客户端调用时代理类(装饰类)与真实类的可见关系。 从目的性或者使用场景上通俗的讲:比如一个CTO为了更好的把控项目开发时间避免程序员欺骗所以他就会找个项目经理,让项目经理去盯紧项目周期,这就是代理。那么如果是装饰呢,那就是往CTO身上加能力,CTO需要知道这个功能用代码实现需要多久那个功能实现需要多久进而把控时间。就是说CTO不仅需要管理也需要代码能力。

代理模式的优缺点

  • 优点
  1. 良好的扩展性。修改被代理角色并不影响调用者使用代理,对于调用者,被代理角色是透明的。
  2. 隔离,降低耦合度。代理角色协调调用者和被代理角色,被代理角色只需实现本身关心的业务,非自己本职的业务通过代理处理和隔离。
  • 缺点
  1. 增加了代理类,实现需要经过代理,因此代码的复杂性和可读性会有所提升。

应用场景

代理模式常见于我们用的框架或者中间件中。比如spring中AOP的实现(•日志拦截•声明式事务处理),mybatis中实现拦截器插件RMI远程方法调用。写到这我突然想起来上古年间我用过的一个技术叫webservice。估计好多小伙伴都没听过这东西。感兴趣的可以查查这个东西,简单的说客户端调用三方服务接口并不知道网络访问细节(究竟访问本地还是远程网络),代理对象对外屏蔽了网络通讯细则。