Spring/Boot/Cloud系列知识(2)——代理模式

代理模式是23种设计模式中的一种,属于一种结构模式。用一句大白话解释这个设计模式:当外部调用者调用A来完成一件事情/一个动作时,并不直接调用A本身,而是调用一个代理者,并再由代理者负责调用真实的执行者A,最终达到间接调用的目的。

代理模式(动态)和Cglib代理是Spring生态中的最基础设计原理之一,所以要了解Spring的工作原理就必须先讨论清楚代理模式的设计思路(无论是静态代理还是动态代理)。

1. 代理模式(静态)

这里写图片描述
(代理模式——静态)

上图显示了一个标准的代理模式(静态)类关系结构。在标准的代理模式(静态)中,至少存在以下角色:

  • Subject接口或抽象角色:对于外部调用者来说只关心调用操作是否被执行,而不会关心本次调用是被直接执行的还是被代理者间接执行的。所以一般来说代理执行者会实现和被代理者相同的接口(或抽象类),“伪装”成被代理的对象(上图中就是伪装成RealSubject类)。

  • RealSubject:被代理的真实业务执行者。简单来说就是真实业务由这个角色负责执行,只是为了在业务处理前后能够执行其它操作,所以真实业务执行者才会被代理(上图中就是被Proxy代理)。

  • Proxy:代理执行者。代理执行者内部引用了真实执行者,并根据需要在真实业务执行前后,执行其它操作:例如判断入参是否符合要求、打开数据库连接、捕获异常发送到事件搜集器中……

以下的代码片段说明了代理模式(静态)中以上几个工作角色的简单实现:

  • Subject接口或抽象角色
// 业务接口定义
public interface Subject {
  // 该方法是被外部调用者所调用的方法
  public void operation();
}
  • Proxy 代理者
/**
 * 代理者,代理者并不执行最终的业务<br>
 * 但是可以在执行最终业务之前、之后做一些其它处理。例如打开数据库连接、提交事务等等
 * @author yinwenjie
 */
public class Proxy implements Subject{
  // 真实执行者
  private RealSubject realSubject;
  // 建议使用slf4j
  private static final Logger LOG = LoggerFactory.getLogger(Proxy.class);

  public Proxy() {
    this.realSubject = new RealSubject();
  }
  public void operation() {
    try {
      LOG.info("在执行真实调用前,可以执行一些动作");
      this.realSubject.operation();
      LOG.info("在执行真实调用后,也可以执行一些动作");
    } catch (Exception e) {
      LOG.info("在执行真实调用异常后,还是可以执行一些动作");
    }
  }
}
  • RealSubject,真实业务在这里执行
// 这是真正的业务执行者
public class RealSubject implements Subject {
  // 建议使用slf4j
  private static final Logger LOG = LoggerFactory.getLogger(RealSubject.class);

  public void operation() {
    this.exec();
  }
  // 真实业务在这个私有方法中执行
  private void exec() {
    LOG.info("执行真实的业务!");
    // 有10%的几率抛出异常
    float currentValue = new Random().nextFloat();
    if(currentValue < 0.01) {
      throw new IllegalArgumentException("抛出了异常");
    }
  }
}
  • 以下代码可以执行这个代理模式设计,并且输出类似如下的结果:
......
public static void main(String[] args) throws Exception {
  Subject subject = new Proxy();
  subject.operation();
}
......

// 以下是可能的输出结果================================
54273 [main] INFO yinwenjie.test.proxy.Proxy  - 在执行真实调用前,可以执行一些动作
54273 [main] INFO yinwenjie.test.proxy.RealSubject  - 执行真实的业务!
54480 [main] INFO yinwenjie.test.proxy.Proxy  - 在执行真实调用异常后,还是可以执行一些动作

2. 代理模式(动态)

静态代理模式很简单吧。实际工作中,静态代理模式只适合写写测试让大家学习和熟悉解决问题的思路,真正有用的还是动态代理模式和Cglib代理,例如Spring中就使用了这两种动态代理方式。本节我们先介绍java.lang.reflect.Proxy代理。

动态代理模式并不要求对某个业务接口(interface)有任何实现,而是一旦有调用发生,就会通知java.lang.reflect.InvocationHandler接口下的invoke方法。

这里写图片描述

2.1、基本代码过程

  • 以下是两个接口定义 TargetOneInterface 和 TargetTwoInterface
// 被代理的接口
public interface TargetOneInterface {
  // 这是一个方法
  public void doSomething();
  // 这是第二个方法
  public void handleSomething();
}
// 第二个需要被代理的接口
public interface TargetTwoInterface {
  // 查询某些数据
  public List<?> findSomething();
  // 按照某种条件进行查询
  public List<?> findSomethingByField(Integer field);
}
  • 接着我们定义代理处理器

java中对动态代理的支持是通过java.lang.reflect.InvocationHandler接口来规范的。以下是一个实现示例:

// 代理者处理器
public class ProxyInvocationHandler implements InvocationHandler {
  /**
   * @param proxy 代理对象,注意是代理者,不是被代理者
   * @param method 被代理的方法
   * @param args 被执行的代理方法中传入的参数
   */
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("====================================================");
    System.out.println("代理者的对象:" + proxy.getClass().getName());
    // 被代理的接口
    Class<?> targetClass = method.getDeclaringClass();
    System.out.println("被代理的接口类:" + targetClass.getName());
    System.out.println("被代理的方法:" + method.getName());
    if(args == null) {
      return null;
    }

    System.out.println("被代理的调用过程参数类型:");
    Arrays.asList(args).stream().forEach((item) -> {
      System.out.println("方法类型:" + item.getClass().getName());
    });

    // 在这里可以返回调用结果 
    return null;
  }
}
  • 接着我们就可以定义如何代理接口了
public class Run {
  public static void main(String[] args) {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    //这是代理处理器
    ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler();
    Object proxy = Proxy.newProxyInstance(classLoader,new Class<?>[]{TargetOneInterface.class , TargetTwoInterface.class}, invocationHandler);

    // 开始调用(测试第一个接口)
    TargetOneInterface targetOne = (TargetOneInterface)proxy;
    targetOne.doSomething();
    targetOne.handleSomething();

    // 开始调用(测试第二个接口)
    TargetTwoInterface targetTwo = (TargetTwoInterface)proxy;
    targetTwo.findSomething();
    targetTwo.findSomethingByField(111);
  }
}

请注意,TargetOneInterface接口和TargetTwoInterface接口可没有定义任何实现。在调用接口中定义的方法时,应用程序也没有执行接口的任何实现,而是调用了InvocationHandler代理处理器中的invoke()方法。这个方法中有三个参数,在以上代码的注释中已经详细说明,这里就不在赘诉了。

  • 以下是执行的结果:
====================================================
代理者的对象:com.sun.proxy.$Proxy0
被代理的接口类:yinwenjie.test.proxy.dproxy.target.TargetOneInterface
被代理的方法:doSomething
====================================================
代理者的对象:com.sun.proxy.$Proxy0
被代理的接口类:yinwenjie.test.proxy.dproxy.target.TargetOneInterface
被代理的方法:handleSomething
====================================================
代理者的对象:com.sun.proxy.$Proxy0
被代理的接口类:yinwenjie.test.proxy.dproxy.target.TargetTwoInterface
被代理的方法:findSomething
====================================================
代理者的对象:com.sun.proxy.$Proxy0
被代理的接口类:yinwenjie.test.proxy.dproxy.target.TargetTwoInterface
被代理的方法:findSomethingByField
被代理的调用过程参数类型:
方法类型:java.lang.Integer

(节后文模拟一个IOC容器,讲解思路。讲解Cglib代理)
============================================这里结束

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

说好不能打脸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值