您的位置:首页 > 其它

4.偏头痛杨的常见设计模式入门系列之代理模式篇

2017-10-25 18:06 387 查看
前戏
代理模式,主要在于“代理”这个词的理解,其实非常简单,举几个生活上的例子:我现在要卖房子,
但我手里的事情非常多,无暇顾及,那我会把房子交给中介中心,让中介中心帮我卖,那中介中心就成了我的代理人,
中介负责打广告,一旦有要买我房子的人出现时,他们会直接联系我的代理人,而不会直接联系我,
我的代理人会带他们去看房子以及讨价还价等等,最后能确定可以成交的时候,我再出面签协议什么的。

“代理”这个词也可以用在网络上,比如,在公司上网,需要设置代理才能上,因为公司屏蔽了外网,
统一由代理介入网络,代理里会判断如果你输入了与工作无关的网站,就给你出提示,不让你访问等等。
还有大名鼎鼎的反向代理,玩过nginx的朋友们肯定不陌生。
还有大名鼎鼎的spring aop,也与代理模式有着千丝万缕的关系。

什么是代理模式
代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。
代理就是一个人或一个机构代表另一个人或者一个机构采取行动。
某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。

当目标对象的功能不足以满足客户端需求时,系统可以为目标对象创建一个代理对象,
代理对象可以增强原目标对象的功能。目标对象与增强代码部分没有硬编码耦合,增加了系统扩展性。
增强部分想加就加,想删就删,对目标对象毫无影响,做到了"可插拔式"编程。
代理模式又分为静态代理与动态代理两种。

实现静态代理
代理对象与被代理对象必须实现同一个接口,在代理对象中可以实现日志等相关服务,
并在需要的时候调用被代理对象,这样被代理对象中就可以仅保存与业务相关的职责。
接口

public interface IHello{
  public void hello(String name);
}

业务逻辑(被代理类)

public class HelloSpeaker

implements IHello{
  public void hello(String name){
    System.out.println("hello,"+name);
  }
}

静态代理类

public class HelloProxy

implements IHello{
  private IHello target;
  public HelloProxy(IHello target){
    this.target = target;
  }
  public void hello(String name){
    System.out.println("日志开始");
    //调用目标对象方法
    target.hello(name);
    System.out.println("日志结束");
  }
}

测试主函数

public static void main(String[] argus){
  //生成代理对象
  IHello helloProxy = new HelloProxy(new HelloSpeaker());
  //调用代理对象的代理方法
  helloProxy.hello("张三");
}

程序中调用的是代理对象,我们会发现被代理类是没有被修改过的,这样就实现了代码解耦,没有硬编码耦合。
在程序较大的情况下,静态代理就无法胜任了。
代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每种方法进行代理。
比如你要为100方法写100个代理类,这100个代理类都是加日志,动态代理就是只写1个代理类即可。

实现动态代理
jdk1.3之后加入了可协助开发动态代理功能的API,不再为特定的对象与方法编写特定的代理对象。
动态代理类可服务于多个目标对象,不再局限于特定对象与接口。

动态代理主要有2个元素:
1.InvocationHandler接口
动态代理类必须实现java.lang.reflect.InvocationHandler接口,并且实现invoke方法,invoke方法相当于代理方法。
2.Proxy类
理解成给目标对象生成代理对象的工具类。

动态代理类

public class LogHandler

implements InvocationHandler
{
  //目标对象&被代理对象
  private Object target;

  //为目标对象创建一个动态代理对象,两个对象实现了相同接口,因此具有相同的方法。
  public <T> T getProxy(Object target) {
    this.target = target;
    return (T)Proxy.newProxyInstance(target.getClass().getClassLoader(),
        target.getClass().getInterfaces(), this);
  }
  
  //必须要实现这个方法,用于生成的代理对象回调此方法。
  //每次调用动态代理对象的方法时都会执行这个invoke方法,
  //参数为:动态代理对象,被代理对象的方法对象,执行参数
  public Object invoke(Object proxy, Method method, Object[]
args) throws Throwable {
    System.out.println("动态代理,日志开始");
    //使用反射回调了目标对象的方法。
    Object result = method.invoke(target, args);
    System.out.println("动态代理,日志结束");
    return result;
  }
}

测试主函数

public static void main(String[] args) {
  LogHandler logHandler = new LogHandler();
  IHello helloProxy = logHandler.getProxy(new HelloSpeaker());
  helloProxy.hello("张三");
}

关联到spring aop
aop中基于动态代理模式,代理对象替换目标对象,进行一系列的增强,将一些与业务无关的可以横切的代码抽出来,
形成切面,然后动态的插入到若干个类中。例如常见的日志记录、事务处理等等。在业务调用时,直接调用的是代理对象。

HelloSpeaker本身的职责只是显示招呼文字,现在需求上要加入日志逻辑,这使得HelloSpeaker的职责加重。
使用代理对象将日志等与业务逻辑无关的动作或任务提取出来,设计形成一个服务对象,像上面的HelloProxy,LogHandler,这样的对象就是切面(Aspect)。

另外spring aop更加灵活,当spring定义InvocationHandler的invoke()时,
并没有以硬编码的方式决定调用哪些增强处理为哪些目标对象增强,而是通过配置文件&注解的形式来决定,
这样就更解耦了,这样代理代码不用改变的情况下,通过动态的配置就可以达到面向切面变成了,这就是框架。

但动态代理有个缺点,就是必须要实现接口,如果没有接口怎么办?请搜:cglib。

总结
代理模式通常用于类似于aop的使用场景,
与业务无关的代码包括:权限控制,系统日志,事务管理,安全检查,缓存,对象池管理等。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: