前言

在Dubbo SPI中是通过Wrapper实现AOP,对于AOP相信大家都不陌生,这里不做的过多的介绍,我们主要来了解Dubbo SPI中是如何使用Wrapper类以及实现的细节。

使用场景

Dubbo 中的一个扩展接口可以有多个扩展实现类,这些扩展实现类可能会包含一些相同的逻辑,如果在每个实现类中都写一遍,那么这些重复代码就会变得很难维护。因此Dubbo提供的自动包装特性(Wrapper),来解决这个问题。 Dubbo将多个扩展实现类的公共逻辑,抽象到Wrapper类中,同时Wrapper也实现了扩展接口,在获取真正的扩展实现对象时,相当于在其外面包装一层Wrapper对象,可以理解成一层装饰器,通过这样就可以在方法执行前后调用公共方法,也是一种AOP的体现。

举个栗子

  1. 定义接口;
@SPI
public interface WrapperDemo {
    void test();
}
  1. 定义接口实现;
public class WrapperDemoImpl implements WrapperDemo{
    @Override
    public void test() {
        System.out.println("WrapperDemoImpl test 方法执行");
    }
}
  1. 创建计算耗时包装类以及Order包装类;
public class CalWasteTimeWrapper implements WrapperDemo {

    private WrapperDemo wrapperDemo;

    public CalWasteTimeWrapper(WrapperDemo wrapperDemo) {
        this.wrapperDemo = wrapperDemo;
    }

    @Override
    public void test() {
        System.out.println("执行cal wrapper");
        System.out.println("执行统计耗时开始");
        long startTime = System.currentTimeMillis();
        wrapperDemo.test();
        System.out.println("执行耗时" + (System.currentTimeMillis() - startTime));
    }
}

public class OrderWrapper implements WrapperDemo {

    private WrapperDemo wrapperDemo;

    public OrderWrapper(WrapperDemo wrapperDemo) {
        this.wrapperDemo = wrapperDemo;
    }

    @Override
    public void test() {
        System.out.println("执行order wrapper");
        wrapperDemo.test();
    }
}
  1. 定义配置文件;
wrapperDemo=org.dubbo.spi.example.wrapper.WrapperDemoImpl
calWasteTimeWrapper=org.dubbo.spi.example.wrapper.CalWasteTimeWrapper
orderWrapper=org.dubbo.spi.example.wrapper.OrderWrapper
  1. 测试,这里我们会发现wrapper的顺序,越往下的越先被调用;
public class Test {
    public static void main(String[] args) {
        WrapperDemo wrapperDemo = ExtensionLoader
                .getExtensionLoader(WrapperDemo.class)
                .getExtension("wrapperDemo");
        wrapperDemo.test();
    }
}

image.png

源码

关于源码部分我们开始介绍时候已经带出来过关于包装类相关的介绍,这里我们主要挑重点代码进行介绍,核心可以分为两步,一步是加载时候,另外执行代码创建时候;

加载

加载就是在loadClass方法时候扩展类加载,判断是否是包装类进行加载,将所有的包装类的信息加载到ConcurrentHashSet中,对于包装类的判断也很简单,就是判断该类里面是否有type类型的构造参数,如果有就是包装类,否则会抛出异常, 返回false;

    //包装类判断
    else if (isWrapperClass(clazz)) {
       //缓存包装类
       cacheWrapperClass(clazz);
    }

    //使用ConcurrentHashSet缓存
    private void cacheWrapperClass(Class<?> clazz) {
        if (cachedWrapperClasses == null) {
            cachedWrapperClasses = new ConcurrentHashSet<>();
        }
        cachedWrapperClasses.add(clazz);
    }

    //判断是否是包装类
    private boolean isWrapperClass(Class<?> clazz) {
        try {
            clazz.getConstructor(type);
            return true;
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

创建

创建部分是在createExtension方法中,对包装类进行创建,对于instance最后生成是WrapperDemo类型对象,里面包含OrderWrapper和CalWasteTimeWrapper对象,所以在最后调用的时候都进行输出,于是乎就有了Aop的效果;

    @SuppressWarnings("unchecked")
    private T createExtension(String name, boolean wrap) {
        //从配置文件中加载所有扩展类,形成配置项名称与配置类的映射关系
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null || unacceptableExceptions.contains(name)) {
            throw findException(name);
        }
        try {
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                //采用反射创建对应实例
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //向对象中注入依赖的属性 IOC自动装配
            injectExtension(instance);

            if (wrap) {
                //创建Wrapper扩展对象
                List<Class<?>> wrapperClassesList = new ArrayList<>();
                if (cachedWrapperClasses != null) {
                    wrapperClassesList.addAll(cachedWrapperClasses);
                    wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                    Collections.reverse(wrapperClassesList);
                }

                if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                    for (Class<?> wrapperClass : wrapperClassesList) {
                        Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                        if (wrapper == null
                                || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                            //将当前instance作为参数创建wrapper实例,然后向wrapper实例中注入属性值
                            //并将wrapper实例赋值给instance
                            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                        }
                    }
                }
            }
            //环境初始化
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

结束

欢迎大家点点关注,点点赞!

Dubbo SPI-Wrapper的更多相关文章

  1. dubbo SPI设计

    SPI 全称为 Service Provider Interface,是一种服务发现机制.SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类.这样可以在运行时, ...

  2. 【Dubbo源码阅读系列】之 Dubbo SPI 机制

    最近抽空开始了 Dubbo 源码的阅读之旅,希望可以通过写文章的方式记录和分享自己对 Dubbo 的理解.如果在本文出现一些纰漏或者错误之处,也希望大家不吝指出. Dubbo SPI 介绍 Java ...

  3. 搞懂Dubbo SPI可拓展机制

    前言 阅读本文需要具备java spi的基础,本文不讲java spi,please google it. 一.Dubbo SPI 简介 SPI(Service Provider Interface) ...

  4. Dubbo 扩展点加载机制:从 Java SPI 到 Dubbo SPI

    SPI 全称为 Service Provider Interface,是一种服务发现机制.当程序运行调用接口时,会根据配置文件或默认规则信息加载对应的实现类.所以在程序中并没有直接指定使用接口的哪个实 ...

  5. Dubbo——SPI及自适应扩展原理

    文章目录 引言 正文 一.什么是SPI? 1. Java SPI的实现 2. Dubbo SPI实现原理 由配置文件得到的猜想 SPI源码 二.自适应扩展机制 三.Dubbo IOC 总结 引言 Du ...

  6. Dubbo SPI源码解析①

    目录 0.Java SPI示例 1.Dubbo SPI示例 2.Dubbo SPI源码分析 ​ SPI英文全称为Service Provider Interface.它的作用就是将接口实现类的全限定名 ...

  7. Dubbo SPI 机制源码分析(基于2.7.7)

    Dubbo SPI 机制涉及到 @SPI.@Adaptive.@Activate 三个注解,ExtensionLoader 作为 Dubbo SPI 机制的核心负责加载和管理扩展点及其实现.本文以 E ...

  8. Dubbo2.7的Dubbo SPI实现原理细节

    总结/朱季谦 本文主要记录我对Dubbo SPI实现原理的理解,至于什么是SPI,我这里就不像其他博文一样详细地从概念再到Java SPI细细分析了,直接开门见山来分享我对Dubbo SPI的见解. ...

  9. 理解 Dubbo SPI 扩展机制

    写在前面 最近接触了 gRPC 体会到虽然众多 RPC 框架各有各的特点但是他们提供的特性和功能有很多的相似之处 , 这就说明他们面对同样的分布式系统带来的问题.从 2016 年左右开始接触到 dub ...

  10. dubbo源码分析--dubbo spi解析

    1. 什么叫SPI? 简单总结就是一种使用类名字符串来动态实例化java类的方式,也就是反射. 2. java SPI与Dubbo SPI有什么区别 (此图来自网上,我没有刻意去截图) 然后在这个文件 ...

随机推荐

  1. 补充--关于nginx服务器多个网站如何设置404的问题?

    补充--关于nginx服务器多个网站如何设置404的问题? 需求1 :设置多个网站404页面为一个 都需配置网站的nginx.conf,以上面的多网站为例,404发布目录下,每个的nginx.conf ...

  2. 使用 Kotlin DSL 编写网络爬虫

    本博文将会通过一个网络爬虫的例子,向你介绍 Kotlin 的基本用法和其简洁有力的 DSL. 关于DSL 按照维基百科的说法,DSL(domain-specific language) 是一种专注于某 ...

  3. HashMap集合的map.values()返回的Collection集合执行add方法报空指针问题

    一.方法1. private Collection<String> setPermissionTenant(List<SysPermission> ls, int tenant ...

  4. Light-Head R-CNN : 旷世提出用于加速two-stage detector的通用结构,速度达102fps

    论文提出Light-Head R-CNN,一个精简的two-stage目标检测器设计准则,在表达能力很强的精简特征图上使用轻量级的R-CNN子网,不仅大量减少推理耗时,还提高了准确率,结合Xcepti ...

  5. 可能是迄今为止最好用的WPF加载动画功能(没有之一)

    前言 当我们在开发应用程序时,用户体验往往是至关重要的一环.在应用程序加载大量数据或执行复杂操作时,为用户提供一个良好的加载体验变得至关重要.加载动画是其中一个有效的方式,它不仅能够告知用户应用程序正 ...

  6. archlinux xfce 设置fcitx5中文输入法详细教程

    1.安装fcitx5 sudo pacman -S fcitx5-im fcitx5-chinese-addons fcitx5-pinyin-zhwiki fcitx5-im 包含fcitx5的一些 ...

  7. 正则表达式环视匹配(?=pattern)、(?!pattern)、(?<=pattern)、(?<!pattern)怎么用

    今天在处理数据的时候遇到一个,需要用正则表达式匹配不包含某字符的字符串的问题,用到否定匹配,现总结如下: 一个正则小知识 ↓ []:表示范围,匹配其中任何一个 {}:表示重复匹配多次. ():表示分组 ...

  8. Scala Reduce操作(简化归约)reduce和fold

    1 package chapter07 2 3 object Test15_HighLevelFunction_Reduce { 4 def main(args: Array[String]): Un ...

  9. HTTP报文结构

    HTTP报文结构 HTTP报文头 HTTP相应报文 请求方法 状态码

  10. 【课程汇总】Hello HarmonyOS系列课程,手把手带你零基础入门

    HarmonyOS是面向未来.面向全场景的新一代智能终端操作系统,为不同设备的智能化.互联与协同提供了统一的语言,给人们带来简洁.流畅.连续.安全可靠的全场景交互体验. 初识HarmonyOS的开发者 ...