代理模式

代理模式

标签 : Java与设计模式


代理模式

为其它对象提供一种代理以控制对这个对象的訪问(能够具体控制訪问某个对象的方法, 在调用这种方法[前/后]做[前/后]置处理, 从而实现将统一流程放到代理类中处理).

  • 我们书写运行一个功能的函数时, 经常须要在当中写入与功能不是直接相关但非常有必要的代码(如日志记录,事务支持等);这些枝节性代码尽管是必要的,但它会带来下面麻烦:

    1. 枝节性代码游离在功能性代码之外,它不是函数的目的,这是对OO是一种破坏;
    2. 枝节性代码会造成功能性代码对其它类的依赖,加深类之间的耦合。会造成功能性代码移植困难,可重用性减少, 这是OO系统所竭力避免的;
    3. 从正常角度来说: 枝节性代码应该监视着功能性代码。然后採取行动。而不是功能性代码通知枝节性代码採取行动,这好比吟游诗人应该是主动记录骑士的功绩而不是骑士主动要求诗人记录自己的功绩

  • Java代理分类

    1. 静态代理: 手动定义代理类
    2. 动态代理: 动态生成代理类
      • JDK自带的动态代理
      • JavaAssist字节码操作库实现
      • CGLib
      • ASM(底层使用指令, 可维护性差)

  • 代理中的角色

    1. 抽象接口:声明真实对象和代理对象的共同接口
    2. 代理对象:代理对象内部包括真实对象的引用,从而能够操作真实对象; 同一时候,代理对象与真实对象有相同的接口,能在不论什么时候取代真实对象,并且代理能够在真实对 象前后加入特定的逻辑以实现功能的扩展;
    3. 真实对象:代理对象所代表的对象;是我们终于要引用的对象

静态代理

我们模拟请明星唱歌这个过程,但大家都知道要请明星唱歌(比方周杰伦)是一件比較麻烦的事情, 比方唱歌前要签约, 唱歌之后还有收款, 而平时明星们都是比較忙的, 想签约, 收款这些事情一般都是由他的助手来代理完毕的,而明星仅仅负责唱歌即可了, 像签约收款这样的事情就能够算作是明星的增强, 尽管这不是明星的主要目的, 可是这个流程是必须要有的.

  • 目标接口
/**
 * 定义真实对象和代理对象的公共接口
 * Created by jifang on 15/12/20.
 */
public interface Star {
    // 签约
    void signContract();

    // 唱歌
    void singSong();

    // 收款
    void collectMoney();
}
  • 真实对象
public class RealStar implements Star {
    /**
     * 因为这些事情都托付给代理来做了, 因此我们仅仅是象征性实现就好了
     */
    @Override
    public void signContract() {
    }

    @Override
    public void collectMoney() {
    }

    /**
     * 但唱歌是要自己真唱的
     */
    @Override
    public void singSong() {
        System.out.println("周杰伦在唱歌");
    }
}
  • 代理对象
    自己并未实现业务逻辑接口,而是调用真实角色来实现:
public class StaticProxy implements Star {

    private Star star;

    public StaticProxy(Star star) {
        this.star = star;
    }

    @Override
    public void signContract() {
        System.out.println("代理签约");
    }

    /**
     * 代理能够帮明星做不论什么事, 但只有唱歌这件事必须由Star自己来完毕
     */
    @Override
    public void singSong() {
        star.singSong();
    }

    @Override
    public void collectMoney() {
        System.out.println("代理收钱");
    }
}
  • Client
public class Client {

    @Test
    public void client() {
        Star star = new StaticProxy(new RealStar());
        star.signContract();
        star.singSong();
        star.collectMoney();
    }
}

能够看出,客户实际想要调用的是RealStarsingSong方法,如今用StaticProxy来代理RealStar类,也能够达到相同的目的,同一时候还封装了其它方法(像singContract``collectMoney)。能够处理一些其它流程上的问题.
假设要依照上述的方法使用代理模式,那么真实角色必须是事先已经存在的。并将其作为代理对象的内部属性;可是实际的Java应用中, 假设有一批真实对象, 而毎个代理对象仅仅相应一个真实对象的话。会导致类的急剧膨胀。此外,假设我们事先并不知道真实角色,那么该怎样使用编写代理类呢?这个问题能够通过java的动态代理机制来解决.


动态代理

所谓动态代理是这样一种class:它是在运行时生成的class。在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface.

JDK对动态代理提供了下面支持:

  • java.lang.reflect.Proxy 动态生成代理类和对象
  • java.lang.reflect.InvocationHandler
    • 能够通过invoke方法实现对真实角色的代理訪问;
    • 每次通过Proxy生成代理类对象时都要指定对象的处理器对象.

首先, Star接口能够精简一下, 仅仅做他该做的事情:

  • Star
/**
 * Star仅仅负责唱歌即可了
 * Created by jifang on 15/12/20.
 */
public interface Star {
    // 唱歌
    void singSong();
}
  • RealStar
public class RealStar implements Star {
    /**
     * 唱歌是要自己真唱的
     */
    @Override
    public void singSong() {
        System.out.println("周杰伦在唱歌");
    }
}

当运行动态代理对象里的方法时, 实际上会替换成调用InvocationHandler中的invoke方法.

  • InvocationHandler: 用于实现代理
/**
 * 相当于原先的代理须要运行的方法
 * Created by jifang on 15/12/20.
 */
public class ProxyHandler implements InvocationHandler {

    private Star star;

    public ProxyHandler(Star star) {
        this.star = star;
    }

    /**
     * 代理对象的实现的所有接口中的方法, 内容都是调用invoke方法
     *
     * @param proxy  代理对象(Proxy.newProxyInstance返回的对象)
     * @param method 当前被调的方法
     * @param args   运行当前方法的參数
     * @return 运行方法method的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("签约");
        Object result = null;
        if (method.getName().equals("singSong")) {
            result = method.invoke(star, args);
        }
        System.out.println("收款");
        return result;
    }
}
  • Client
public class Client {

    @Test
    public void client() {
        /**
         * newProxyInstance方法会动态生成一个代理类, 他实现了Star接口, 然后创建该类的对象.
         *
         * 三个參数
         * 1. ClassLoader: 生成一个类, 这个类也须要载入到方法区中, 因此须要指定ClassLoader来载入该类
         * 2. Class[] interfaces: 要实现的接口
         * 3. InvocationHandler: 调用处理器
         */
        Star proxyStar = (Star) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Star.class}, new ProxyHandler(new RealStar()));
        proxyStar.singSong();
    }
}

代理工厂实现动态代理

  • 动态代理尽管能够使得我们不用在手写代理对象的代码,可是InvocationHandler还是面向特定的抽象接口(如Star)的来写的; 而代理工厂能够让我们的代码写的更加抽象(而不必面向确定的抽象接口写代码).
  • 代理工厂的目标是目标对象和增强方法皆可改变, 这个模式在现实中的表现就是:
    a. 明星对代理并不一定是从一而终的, 明星随时都可能会换代理(助手);
    b. 明星不一定仅仅会唱歌, 他还有可能会跳舞.
    c. 代理可能不仅仅是为一个明星服务
    这样, 我们就实现一个代理工厂-能够任意更换代理所做的辅助性工作; 而目标对象也能够随时添加新的方法.

    能够看到, ProxyFactoryStart是没有不论什么关系的, 他们之间能够联系其它全然是靠Client来促成.


  • 代理工厂

/**
 * Created by jifang on 15/12/21.
 */
public class ProxyFactory {

    private BeforeAdvice beforeAdvice;
    private Object targetObject;
    private AfterAdvice afterAdvice;

    public ProxyFactory() {
    }

    public ProxyFactory(BeforeAdvice beforeAdvice, Object targetObject, AfterAdvice afterAdvice) {
        this.beforeAdvice = beforeAdvice;
        this.targetObject = targetObject;
        this.afterAdvice = afterAdvice;
    }

    private InvocationHandler handler = new InvocationHandler() {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (beforeAdvice != null) {
                beforeAdvice.before();
            }

            Object result = null;
            if (targetObject != null) {
                result = method.invoke(targetObject, args);
            }

            if (afterAdvice != null) {
                afterAdvice.after();
            }

            return result;
        }
    };

    public Object createProxy() {
        return Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), targetObject.getClass().getInterfaces(), handler);
    }
}
  • Client
    StarRealStar同前
/**
 * Created by jifang on 15/12/20.
 */
public class Client {

    @Test
    public void client() {
        Star star = (Star) new ProxyFactory(new StarBeforeAdvice(), new RealStar(), new StarAfterAdvice()).createProxy();
        star.singSong();
    }

    /**
     * BeforeAdvice实现可定制化
     */
    private static class StarBeforeAdvice implements BeforeAdvice {

        @Override
        public void before() {
            System.out.println("签合约");
        }
    }

    /**
     * AfterAdvice实现可定制化
     */
    private static class StarAfterAdvice implements AfterAdvice {

        @Override
        public void after() {
            System.out.println("收款");
        }
    }
}

如今, 我们的对明星要求比較高了, 他不光要会唱歌, 还要会跳舞.

public interface Star {
    // 唱歌
    void singSong();

    // 跳舞
    void dancing();
}
public class RealStar implements Star {
    //...

    @Override
    public void dancing() {
        System.out.println("周杰伦在跳舞...");
    }
}

此时, 我们的client什么都不须要改, 仅仅是加入一个调用就可:

public class Client {

    @Test
    public void client() {
        Star star = (Star) new ProxyFactory(new StarBeforeAdvice(), new RealStar(), new StarAfterAdvice()).createProxy();
        star.singSong();
        star.dancing();
    }

    // ...
}

并且在实际开发中, 这些增强类还能够从配置文件里读取(像Spring).
这样的代理在AOP(Aspect Orient Programming: 面向切面编程)中被成为AOP代理,AOP代理包括了目标对象的所有方法, 但AOP代理中的方法与目标对象的方法存在差异: 比方能够在运行目标方法之前/后插入一些通用的处理(增强).


代理场景

  • 当Client须要调用某个对象时,客户端实际上也不关心是否准确得到该对象,Client要仅仅是一个能提供该功能的对象而已,因此我们就可返回该对象的代理(Proxy).代理就是在訪问对象时引入一定程度的间接性, 因为存在这样的间接性, 我们就能够做非常多工作:
    1. 远程代理: 为一个对象在不同的地址空间提供局部代表, 这样能够隐藏一个对象存在于不同地址空间的事实(Dubbo实现);
    2. 安全代理: 屏蔽对真实角色的訪问, 用代理来控制对真实对象的訪问权限;
    3. 延迟载入: 先载入轻量级代理对象,真正须要时再载入真实对象.

參考:
你应该知道的 RPC 原理
大话设计模式
高琪讲设计模式
崔希凡讲动态代理
java代理机制
经常使用设计模式的应用场景
posted @ 2017-08-05 16:05  wzjhoutai  阅读(175)  评论(0编辑  收藏  举报