Spring/Boot/Cloud系列知识(2)— — 代理模式
2019-07-17 10:49
471 查看
本文转自:https://blog.csdn.net/yinwenjie/article/details/77848285
代理模式是23种设计模式中的一种,属于一种结构模式。用一句大白话解释这个设计模式:当外部调用者调用A来完成一件事情/一个动作时,并不直接调用A本身,而是调用一个代理者,并再由代理者负责调用真实的执行者A,最终达到间接调用的目的。
代理模式(动态)和Cglib代理是Spring生态中的最基础设计原理之一,所以要了解Spring的工作原理就必须先讨论清楚代理模式的设计思路(无论是静态代理还是动态代理)。
1. 代理模式(静态)
(代理模式——静态)
上图显示了一个标准的代理模式(静态)类关系结构。在标准的代理模式(静态)中,至少存在以下角色:
- Subject接口或抽象角色:对于外部调用者来说只关心调用操作是否被执行,而不会关心本次调用是被直接执行的还是被代理者间接执行的。所以一般来说代理执行者会实现和被代理者相同的接口(或抽象类),“伪装”成被代理的对象(上图中就是伪装成RealSubject类)。
- RealSubject:被代理的真实业务执行者。简单来说就是真实业务由这个角色负责执行,只是为了在业务处理前后能够执行其它操作,所以真实业务执行者才会被代理(上图中就是被Proxy代理)。
- Proxy:代理执行者。代理执行者内部引用了真实执行者,并根据需要在真实业务执行前后,执行其它操作:例如判断入参是否符合要求、打开数据库连接、捕获异常发送到事件搜集器中……
以下的代码片段说明了代理模式(静态)中以上几个工作角色的简单实现:
- Subject接口或抽象角色
[code]// 业务接口定义 public interface Subject { // 该方法是被外部调用者所调用的方法 public void operation(); }
- Proxy 代理者
[code]/** * 代理者,代理者并不执行最终的业务<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,真实业务在这里执行
[code]// 这是真正的业务执行者 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("抛出了异常"); } } }
- 以下代码可以执行这个代理模式设计,并且输出类似如下的结果:
[code]...... 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
[code]// 被代理的接口 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接口来规范的。以下是一个实现示例:
[code]// 代理者处理器 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; } }
- 接着我们就可以定义如何代理接口了
[code]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()方法。这个方法中有三个参数,在以上代码的注释中已经详细说明,这里就不在赘诉了。
- 以下是执行的结果:
[code]==================================================== 代理者的对象: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
<==== 上一篇 =====>
Spring/Boot/Cloud系列知识(1)— — 开篇
<==== 下一篇 =====>
Spring/Boot/Cloud系列知识(3)— — 代理模式(中)
相关文章推荐
- Spring/Boot/Cloud系列知识(4)— — 代理模式(下)
- Spring/Boot/Cloud系列知识(7)— — Spring Aspectj EL(3)
- Spring/Boot/Cloud系列知识(1)——开篇
- Spring/Boot/Cloud系列知识(5)— — Spring EL(1)
- Spring/Boot/Cloud系列知识(6)— — Spring EL(2)
- 【微框架】之一:从零开始,轻松搞定SpringCloud微服务系列--开山篇(spring boot 小demo)
- spring boot&&cloud干货系列
- SpringCloud微服务知识整理二:微服务构建-SpringBoot
- spring boot&&cloud干货系列
- SpringCloud微服务实战系列二:微服务构建-SpringBoot
- SpringBoot 使用@Aspect进行日志管理(基于反射代理模式+注解Log)
- springboot系列 springcloud和springboot学习资料整理
- 防盗链与springboot代理模式(图片文件转发)
- [置顶] Spring/Boot/Cloud系列知识(3)——代理模式(中)
- Spring Boot2.0系列教程合集、Spring Cloud系列教程合集、Spring Boot常见错误合集、Spring Cloud常见错误合集
- [置顶] Spring/Boot/Cloud系列知识(4)——代理模式(下)
- spring boot&&cloud干货系列
- spring boot&&cloud干货系列
- [置顶] Spring/Boot/Cloud系列知识(2)——代理模式
- SpringCloud系列九:SpringCloudConfig 基础配置(SpringCloudConfig 的基本概念、配置 SpringCloudConfig 服务端、抓取配置文件信息、客户端使用 SpringCloudConfig 进行配置、单仓库目录匹配、应用仓库自动选择、仓库匹配模式)