Dubbo-SPI和AOP的前世今生

栏目: 编程工具 · 发布时间: 6年前

内容简介:本篇是假如我们就以

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

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

插播面试题

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

直入主题

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

简单做法

public class EmployeeServiceImpl implements IEmployeeService {

    private TransactionManager txManager;

    @Override
    public void save() {
        try {
            txManager.begin();
            System.out.println("保存操作");
            txManager.commit();
        }catch (Exception e){
            txManager.rollback();
            e.printStackTrace();
        }
    }

    @Override
    public void update() {
        try {
            txManager.begin();
            System.out.println("更新操作");
            txManager.commit();
        }catch (Exception e){
            txManager.rollback();
            e.printStackTrace();
        }
    }
}
复制代码

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

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

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

装饰设计模式

Dubbo-SPI和AOP的前世今生
public class APP {

    @Test
    public void testSave() throws Exception {
        IEmployeeService service = new EmployeeServiceImplWapper(new TransactionManager(),
                new EmployeeServiceImpl());
        service.save();
    }

    @Test
    public void testUpdate() throws Exception {
        IEmployeeService service = new EmployeeServiceImplWapper(new TransactionManager(),
                new EmployeeServiceImpl());
        service.update();
    }
}
复制代码

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

静态代理

Dubbo-SPI和AOP的前世今生

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

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

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

动态代理

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

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

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class JDKProxyTest {

    @Autowired
    private TransactionManagerInvocationHandle handle;

    @Test
    public void testSave() throws Exception {
        IEmployeeService service = handle.getProxyObject();
        service.save();
    }

    @Test
    public void testUpdate() throws Exception {
        IEmployeeService service = handle.getProxyObject();
        service.update();
    }
}
复制代码
public class TransactionManagerInvocationHandle implements InvocationHandler {

    @Setter
    private TransactionManager txManager;
    @Setter
    private Object target;//真实对象

    //生成代理对象
    //泛型只是为了调用时不用强转,如果用Object的话调用时需要强转
    public <T> T getProxyObject() {
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(),//类加载器
                target.getClass().getInterfaces(),//为哪些接口做代理(拦截什么方法)
                this);//为哪个类监听增强操作的方法(把这些方法拦截到哪里处理)
    }

    //如何做增强操作(被拦截的方法在这里增强处理)
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object obj = null;

        try {
            txManager.begin();
            //原封不动调用之前的方法
            obj = method.invoke(target, args);
            txManager.commit();
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
            txManager.rollback();
        }

        return obj;
    }
}
复制代码

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

dubbo-spi-aop

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

//dubbo spi中的aop
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
复制代码

下面引用文档介绍

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

Wrapper类内容:

package com.alibaba.xxx;

import com.alibaba.dubbo.rpc.Protocol;

public class XxxProtocolWrapper implemenets Protocol {
    Protocol impl;

    public XxxProtocol(Protocol protocol) { impl = protocol; }

    // 接口方法做一个操作后,再调用extension的方法
    public void refer() {
        //... 一些操作
        impl.refer();
        // ... 一些操作
    }

    // ...
}
复制代码

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

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

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

敲黑板划重点-小技巧

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

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

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

//环绕
try {
    //前置
    System.out.println("=====");
    //后置
}catch (Exception e){
    //异常
}finally {
    //最终
}
复制代码

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

写在最后

肥朝 是一个专注于 原理、源码、开发技巧的技术公众号,号内原创专题式源码解析、真实场景源码原理实战(重点)。 扫描下面二维码 关注肥朝,让本该造火箭的你,不再拧螺丝!

Dubbo-SPI和AOP的前世今生

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First JavaScript Programming

Head First JavaScript Programming

Eric T. Freeman、Elisabeth Robson / O'Reilly Media / 2014-4-10 / USD 49.99

This brain-friendly guide teaches you everything from JavaScript language fundamentals to advanced topics, including objects, functions, and the browser’s document object model. You won’t just be read......一起来看看 《Head First JavaScript Programming》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换