[肥朝]Dubbo-SPI和AOP的前世此生

前言

本篇是spi的第四篇,本篇讲解的是spi中增长的AOP,仍是和上一篇同样,咱们先从你们熟悉的spring引出AOP.java

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

插播面试题

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

直入主题

假如咱们就以AOP最经常使用的场景事务来讲,咱们最初的作法是怎么样的?spring

简单作法

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();
        }
    }
}
复制代码

这些代码存在的问题就很明显了,好比设计模式

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

优化代码咱们第一个想到的是设计模式,那么咱们进入以下的优化缓存

装饰设计模式

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,这样就很不安全,那么咱们可不能够把真实对象隐藏起来,让使用者看不到呢?那么咱们进一步优化安全

静态代理

经过这种方式,真实对象对使用者进行了必定的隐藏,可是又引出了新的问题架构

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

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

动态代理

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

动态代理的方式和区别咱们前面有讲过,这里就简单演示一下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种方式,这也是我以前反复强调的,理解透原理和设计思想,不少东西都是一通百通的.

写在最后

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

相关文章
相关标签/搜索