dubbo源码解析-spi(四)

比眉伴天荒 2022-04-24 18:10 361阅读 0赞

Dubbo源码解析系列文章均来自肥朝简书

前言

本篇是spi的第四篇,本篇讲解的是spi中增加的AOP,还是和上一篇一样,我们先从大家熟悉的spring引出AOP.

AOP是老生常谈的话题了,思想都不会是一蹴而就的.比如架构设计从All in OneSOA也是一个逐步演进的过程,所以本篇也讲讲这个AOP的思想演进过程.

插播面试题

  • 你提到了dubbo中spi也增加了AOP,那你讲讲这用到了什么设计模式,dubbo又是如何做的.

直入主题

假如我们就以AOP最常用的场景事务来说,我们最初的做法是怎么样的?

简单做法

  1. public class EmployeeServiceImpl implements IEmployeeService {
  2. private TransactionManager txManager;
  3. @Override
  4. public void save() {
  5. try {
  6. txManager.begin();
  7. System.out.println("保存操作");
  8. txManager.commit();
  9. }catch (Exception e){
  10. txManager.rollback();
  11. e.printStackTrace();
  12. }
  13. }
  14. @Override
  15. public void update() {
  16. try {
  17. txManager.begin();
  18. System.out.println("更新操作");
  19. txManager.commit();
  20. }catch (Exception e){
  21. txManager.rollback();
  22. e.printStackTrace();
  23. }
  24. }
  25. }

这些代码存在的问题就很明显了,比如

  • 处理事务的代码大量重复
  • 根据责任分离思想,在业务方法中,只需要处理业务功能,不该处理事务.

优化代码我们第一个想到的是设计模式,那么我们进入如下的优化

装饰设计模式

![Image 1][]

  1. public class APP {
  2. @Test
  3. public void testSave() throws Exception {
  4. IEmployeeService service = new EmployeeServiceImplWapper(new TransactionManager(),
  5. new EmployeeServiceImpl());
  6. service.save();
  7. }
  8. @Test
  9. public void testUpdate() throws Exception {
  10. IEmployeeService service = new EmployeeServiceImplWapper(new TransactionManager(),
  11. new EmployeeServiceImpl());
  12. service.update();
  13. }
  14. }

通过装饰设计模式,我们解决了上面遇到的两个问题,但是同时也引出了新的问题,在客户端我们暴露了真实的对象EmployeeServiceImpl,这样就很不安全,那么我们可不可以把真实对象隐藏起来,让使用者看不到呢?那么我们进一步优化

静态代理

![Image 1][]

通过这种方式,真实对象对使用者进行了一定的隐藏,但是又引出了新的问题

  • 如果需要代理的方法很多,则每一种都要处理.比如图中只处理了save方法,万一有很多方法,则需要处理很多次
  • 接口新增了方法后,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法(EmployeeServiceImplEmployeeServiceImplProxy都要改动),增加了代码的维护难度
  • 代理对象的某个接口只服务于某一种类型的对象,比如EmployeeServiceImplProxy是只给IEmployeeService接口服务的,假如我新增了一个IRoleService,又要搞一个RoleServiceImplProxy,增加了维护难度

鉴于以上问题,我们能否再优化一下呢?答案是可以的

动态代理

动态代理类是在程序运行期间由JVM通过反射等机制动态的生成的,所以不存在代理类的字节码文件.代理对象和真实对象的关系是在程序运行事情才确定的.

动态代理的方式和区别我们前面有讲过,这里就简单演示一下jdk动态代理

  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration
  3. public class JDKProxyTest {
  4. @Autowired
  5. private TransactionManagerInvocationHandle handle;
  6. @Test
  7. public void testSave() throws Exception {
  8. IEmployeeService service = handle.getProxyObject();
  9. service.save();
  10. }
  11. @Test
  12. public void testUpdate() throws Exception {
  13. IEmployeeService service = handle.getProxyObject();
  14. service.update();
  15. }
  16. }
  17. public class TransactionManagerInvocationHandle implements InvocationHandler {
  18. @Setter
  19. private TransactionManager txManager;
  20. @Setter
  21. private Object target;//真实对象
  22. //生成代理对象
  23. //泛型只是为了调用时不用强转,如果用Object的话调用时需要强转
  24. public <T> T getProxyObject() {
  25. return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(),//类加载器
  26. target.getClass().getInterfaces(),//为哪些接口做代理(拦截什么方法)
  27. this);//为哪个类监听增强操作的方法(把这些方法拦截到哪里处理)
  28. }
  29. //如何做增强操作(被拦截的方法在这里增强处理)
  30. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  31. Object obj = null;
  32. try {
  33. txManager.begin();
  34. //原封不动调用之前的方法
  35. obj = method.invoke(target, args);
  36. txManager.commit();
  37. return obj;
  38. } catch (Exception e) {
  39. e.printStackTrace();
  40. txManager.rollback();
  41. }
  42. return obj;
  43. }
  44. }

这样,对于使用者来说,就无需再关心事务的逻辑.当然这个还需要getProxyObject获取动态代理对象是不是还是太麻烦,那如何不调用getProxyObject就无声无息的注入动态代理对象呢?可以观看之前的dubbo源码解析-简单原理、与spring融合

dubbo-spi-aop

看了这么多演进的过程,是不是还是没有看到dubbo源码的影子?因为dubbo在做spi的设计的时候,也是有一个演进和优化的过程的.我们来看看dubbo是怎么做的

  1. //dubbo spi中的aop
  2. instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));

下面引用文档介绍

ExtensionLoader 在加载扩展点时,如果加载到的扩展点有拷贝构造函数,则判定为扩展点 Wrapper 类。

Wrapper类内容:

  1. package com.alibaba.xxx;
  2. import com.alibaba.dubbo.rpc.Protocol;
  3. public class XxxProtocolWrapper implemenets Protocol {
  4. Protocol impl;
  5. public XxxProtocol(Protocol protocol) { impl = protocol; }
  6. // 接口方法做一个操作后,再调用extension的方法
  7. public void refer() {
  8. //... 一些操作
  9. impl.refer();
  10. // ... 一些操作
  11. }
  12. // ...
  13. }

通过 Wrapper 类可以把所有扩展点公共逻辑移至 Wrapper 中。新加的 Wrapper 在所有的扩展点上添加了逻辑,有些类似 AOP,即 Wrapper 代理了扩展点。

看到这里可能发现,dubbo里面的spi增加的aop,其实就是装饰者设计模式.但是从上面的演进中我们发现,装饰者设计模式还是有很多弊端的,后面是逐步演进,最后到达动态代理.那dubbo又是如何处理这个弊端逐步演进的?

dubbo里面有个概念叫扩展点自适应,也就是给接口注入拓展点是一个Adaptive实例,直到方法执行时,才决定调用的是哪一个拓展点的实现.这个在下一篇的Adaptive会详细介绍,本篇其实也是下一篇的启蒙篇.

敲黑板划重点-小技巧

既然本篇提到了spring的aop,那么这里插播一个小技巧,Spring的AOP增强方式一共有5种,分别为






























增强类型 应用场景
前置增强 权限控制、记录调用日志
后置增强 统计分析结果数据
异常增强 通过日志记录方法异常信息
最终增强 释放资源
环绕增强 缓存、性能、权限、事务管理

面试的时候也会问到5种增强方式,但是很多同学都是说,我每天都在加班,哪有时间记这些.但是其实如果你理解他的设计思想,那么就可以”理解性记忆”,以后想忘都忘不掉.

  1. //环绕
  2. try {
  3. //前置
  4. System.out.println("=====");
  5. //后置
  6. }catch (Exception e){
  7. //异常
  8. }finally {
  9. //最终
  10. }

其实他这5种方式就是根据try-catch-finally的模型来设计的,只要你记住了这个设计的思想,自然不会忘记这5种方式,这也是我之前反复强调的,理解透原理和设计思想,很多东西都是一通百通的.

写在最后

spi写了四篇,本篇为结束篇,下周开始下一个关键词Adaptive,期待继续与你相遇,鉴于肥朝才疏学浅,不足之处还望斧正

[Image 1]:

发表评论

表情:
评论列表 (有 0 条评论,361人围观)

还没有评论,来说两句吧...

相关阅读

    相关 dubbo解析-SPI机制

     架构体系   框架介绍   概述   Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spri

    相关 dubbo解析-spi(五)

    Dubbo源码解析系列文章均来自肥朝简书 前言 之前对dubbo的`SPI`进行了四篇的分享.大家对这个概念有了一些初步的了解.谈到编程水平如何进阶,大家可能都会异口同