代理模式

代理模式

标签 : 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 @ 2015-12-27 11:27  挨踢人啊  阅读(156)  评论(0编辑  收藏  举报