设计模式-代理模式

218 阅读5分钟
原文链接: zhuanlan.zhihu.com

What

代理模式(Surrogate,Proxy Pattern),也叫委托模式,为另一个对象提供一个替身或占位符以控制这个对象的访问。

一个代理类可以代理多个被委托者或被代理者,因此一个代理类具体代理哪个真实主题角色,是由场景类决定的。

参与者(三个角色)

Subject(抽象主题角色)

抽象主题类可以是抽象类,也可以是接口,是一个最普通的业务类型定义,无特殊要求。

其中的doSomething是一种标识方法,可以有多个逻辑处理方法。

public interface Subject {
    /**
     * 业务操作
     * @param str   操作信息
     */
    public void doSomething(String str);
}

RealSubject(具体主题角色)

也叫做被委托角色,被代理角色,是业务逻辑的具体执行者。

public class RealSubject implements Subject {
    /**
     * 业务操作
     *
     * @param str   操作信息
     */
    @Override
    public void doSomething(String str) {
        System.out.println("do something!--->" + str);
    }
}

Proxy(代理主题角色)

也叫做委托类,代理类。 它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并且在真实主题角色处理完毕前后做预处理和善后处理工作。

  • 保存一个引用使得代理可以访问实体。
  • 提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体。
  • 控制对实体的存取,并可能负责创建和删除它。
public class SubjectDynamicProxy extends DynamicProxy {

    public static <T> T newProxyInstance(Subject subject){
        /* 获得ClassLoader */
        ClassLoader loader = subject.getClass().getClassLoader();

        /* 获得接口数组 */
        Class<?>[] classes = subject.getClass().getInterfaces();

        /* 获得handler */
        InvocationHandler handler = new MyInvocationHandler(subject);
        return newProxyInstance(loader, classes, handler);
    }
}

Why

动机

是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截和过滤。

对一个对象进行访问控制的一个原因是为了只有在我们确实需要这个对象时才对它进行创建和初始化。

这一限制条件意味着,对于每一个开销很大的对象,应该根据需要进行创建,当一个图像变为可见时会产生这样的需要。

问题的解决方案是使用另一个对象,即Proxy。

代理模式的优点

  • 职责清晰

真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件事务,附带的结果就是编程简洁清晰。

  • 高扩展性

只要实现了接口,甭管它如何变化,都逃不脱接口的掌控

  • 智能化

动态代理

How

使用代理模式, 创建代表(representative)对象,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。

实现动态代理

动态代理是在是现阶段不用关心代理谁,而在运行阶段才指定代理哪一个对象。要实现动态代理的首要条件是:被代理类必须实现一个接口。

现在有一个非常流行的名称叫做切面编程,也就是AOP(Aspect Oriented Progamming),其核心就是采用了动态代理机制。

动态代理类插入了较多的AOP术语,比如在什么地方(连接点)执行什么行为(通知)。

public class DynamicProxy<T> {

    public static <T> T newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h){
        /** 寻找JoinPoint连接点,AOP框架使用元数据定义 **/
        if (true) {
            // 执行一个前置通知
            (new BeforeAdvice()).exec();
        }

        /** 执行目标,并返回结果 **/
        return (T) Proxy.newProxyInstance(loader, interfaces, h);
    }
}

InvocationHandler是JDK提供的动态代理接口,对被代理的方法进行代理。其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用。

动态代理是根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”。默认情况下所有的方法返回值都是空的,代理已经实现它了,但是没有任何的逻辑含义,通过InvocationHandler接口,所有方法都由该Handler来处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务。

public class MyInvocationHandler implements InvocationHandler {
    /** 被代理的对象 **/
    private Object target = null;

    /** 通过构造函数传递一个对象 **/
    public MyInvocationHandler(Object _obj) {
        this.target = _obj;
    }

    /** 代理方法 **/
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /** 执行被代理的方法 **/
        return method.invoke(this.target, args);
    }
}

通知Advice,就是要切入的类。

通知Advice从另一个切面切入,最终在高层模块也就是Client进行耦合,完成逻辑的封装任务。

public interface IAdvice {
    /** 通知只有一个方法,执行即可 **/
    public void exec();
}
public class BeforeAdvice implements IAdvice {
    @Override
    public void exec() {
        System.out.println("我是前置通知,我被执行了!!!");
    }
}

动态代理实现代理的职责,业务逻辑Subject实现相关的逻辑功能,两者之间没有必然的相互耦合的关系。Client代码如下,

public class Client {

    public static void main(String[] args) throws Exception {
        /** 动态代理的场景类 **/
        // 定义一个主题
        Subject subject = new RealSubject();

        // 定义主题的代理
        Subject proxy = SubjectDynamicProxy.newProxyInstance(subject);

        // 代理的行为
        proxy.doSomething("Finish");

        /** end of 动态代理的场景类 **/
    }
}

以上的动态代理是一个通用代理框架,主要意图就是解决我们常说的“审计”问题,也就是横切面编程,在不改变已有代码的情况下增强或控制对象的行为。

GitHub SourceCode