Dubbo SPI学习笔记

Dubbo SPI

微服务相关概念

微服务

先了解不用微服务,也就是版本之前的单体应用。大家开发程序,都是在一个项目下进行,如下:

项目结构:

image-20231211102546991

可以看到,各个模块(服务)都是在同一台机器上运行的(ip相同)

采用idea进行开发:

对应用进行微服务拆分后:

对系统进行分析,可以将服务进行拆分,分出来的就是一个一个的微服务,然后放到不同的机器上去运行。

微服务好处:高性能,高可用。

微服务缺点:协调是个大问题。

集群

同一个服务(服务在不同开发团队中所表示的含义可能不一样)在多个机器上跑,就构成一个集群。

如下:

3台机器都跑同样的搜索模块,如果一个机器能承载100qps的流量,那么三台就可以承载300qps,大幅度提高了性能。


当然,单体应用,如果跑在了多个机器上,也可以称为集群,不过一般不这么干。

分布式

单体应用:如最开始的图片所示

分布式应用:将单体应用拆开,在不同的机器上提供不同的功能,这就是分布式

分布式好处:稳定,性能好,速度快

分布式坏处:消息同步始终是个问题,这里的消息是很宽泛的概念,不光指微服务之间的通信,还涉及到多个数据库数据的同步,注册中心实例之间的同步等,只要是分布式(或者也可以称为采用了集群)都要考虑到这个问题

总结

微服务、分布式、集群在企业中都是相伴随出现的。

零散概念

  • API

API(Application Programming Interface,应用程序编程接口)是一个软件组件或系统提供给其他组件或系统调用的一组定义。API定义了组件之间的交互接口,包括输入参数、输出结果等,使得不同组件可以相互通信和交互。

  • SPI

SPI(Service Provider Interface,服务提供者接口)是一种扩展机制,用于允许组件的开发者定义一组接口,供第三方开发者实现并扩展原有组件的功能。SPI主要用于实现组件的插件化和扩展性,允许在运行时动态加载并替换组件。

API和SPI对比

API和SPI解决的问题方面略有不同:

  • API主要解决的是组件之间的交互和通信问题。通过定义一组接口,API规定了组件之间的调用规范和数据传输格式,使得组件能够直接使用和调用其他组件提供的功能,将复杂的功能封装起来提供给开发者使用。
  • SPI主要解决的是组件的扩展性和插件化问题。通过定义一组接口作为扩展点,SPI允许第三方开发者实现和替换组件的具体功能,从而扩展组件的能力,而无需修改组件的源代码。

API和SPI的一些对比:

  1. 调用者角色不同:API是供调用者使用的,用来调用已经实现的功能;而SPI是供其他开发者进行扩展和实现的,用来扩展组件的功能。
  2. 可见性差异:API是对外公开的,交互规范和使用方式都是已经定义好的;SPI则是为了让第三方开发者自定义来实现的,一般对于核心组件的SPI,会在公开文档中进行披露,供开发者参考。
  3. 耦合性差异:API是组件之间的耦合点,是接口的消费者依赖的,对于实现者来说,API是不可变的;而SPI是组件的解耦点,具体实现的选择是由第三方开发者决定的,可以在运行时进行切换和替换。
  • KPI

KPI 是一种衡量和评估组织或个人绩效的指标。它是用来衡量目标是否实现、工作是否有效以及业绩是否达到预期结果。KPI 可以是具体的数字、比率、百分比或其他衡量指标,旨在提供可量化的参考指标以评估绩效。

  • OKR

OKR 是一种目标管理框架,它由目标(Objectives)和关键结果(Key Results)组成。目标是明确的、定性的描述,表达出想要实现的愿景和目的。关键结果则是具体的、可衡量的结果,用来衡量目标的实现程度以及进展情况。通常每个目标都会设定一到多个关键结果。

KPI和OKR对比

  1. 定义方式:KPI 使用指标的方式来衡量绩效,而 OKR 使用目标和关键结果的方式来定义和衡量绩效。
  2. 可量化性:KPI 是量化的指标,可以直接对绩效进行量化评估;而 OKR 的目标是定性的描述,关键结果是可量化的,综合关键结果的情况来评估目标的实现情况。
  3. 设定范围:KPI 可以应用于整个组织、部门或个人层面,用于衡量整体绩效;而 OKR 更适用于团队或个人层面,用于设定并推动特定目标的实现。
  4. 强调重点:KPI 强调业绩评估和指标达成情况,关注结果;而 OKR 强调目标的设定、推动和完成情况,关注绩效过程。
  5. 灵活性:KPI 可以具有相对稳定的设定周期和权重,适于长期绩效评估;而 OKR 通常设定在较短的时间周期内(比如季度),便于快速反馈和调整目标。
  6. 沟通和合作:OKR 通常设定为公开透明的,能够促进团队内外的沟通和合作;而 KPI 的设定和评估可能更多地是个人或管理者之间的交流。

Dubbo介绍

理论

Dubbo 是一种高性能、轻量级的开源分布式服务框架,由阿里巴巴公司开发并贡献给 Apache 软件基金会。它提供了分布式服务治理的核心功能,使得开发者可以更轻松地构建和管理大规模分布式架构下的服务。

以下是 Dubbo 的一些详细介绍:

  1. 分布式服务治理:Dubbo 提供了一整套分布式服务治理的解决方案,包括服务注册与发现、负载均衡、路由、容错和自动容灾等功能。通过这些功能,Dubbo 可以简化分布式系统中服务的管理和运维,提高系统的弹性和可靠性。
  2. 高性能和低延迟:Dubbo 在设计之初就注重性能和低延迟的实现。其内部使用了多种优化技术,包括使用高效的序列化协议、灵活的线程模型以及基于 Netty 的高性能网络传输,从而将通信开销降到最低,提供快速响应和高吞吐量。
  3. 扩展性和插件化:Dubbo 通过 SPI(Service Provider Interface)机制为扩展提供了便捷的方式。开发者可以根据需要自行实现扩展插件,包括协议、序列化、负载均衡、容错、注册中心等。这使得 Dubbo 的功能可以灵活地根据应用程序的需求进行扩展和定制。
  4. 支持多种通信协议和序列化协议:Dubbo 不仅支持常见的 RPC 通信协议如 TCP、HTTP、Hessian 等,还支持其他开源协议和标准协议(如 gRPC、RESTful 等)。在序列化方面,Dubbo 支持多种序列化协议(如 Java 原生序列化、JSON、Hessian、Protobuf 等),方便与不同语言和系统间进行兼容性互调。
  5. 高度可扩展的生态系统:Dubbo 生态系统非常丰富,涵盖了大量开源工具和框架的集成,如 Spring Framework、Spring Boot、MyBatis 等。这使得开发者可以在 Dubbo 的基础上,借助这些工具和框架,更方便地开发和部署分布式应用。

实践

利用dubbo开启一个简单分布式应用。

可以参考dubbo官网快速搭建一个demo应用。以下是我自己的,仅供部分参考,主要讲解思路。

https://start.dubbo.apache.org/bootstrap.html

下面是我自己写的,跟官方的不一样,可以先参考一下这个简单demo

  • 项目结构

    dubbo-interface:定义统一的接口

dubbo-provider:实现接口,也就是对外提供服务

要使用@DubboService注解

dubbo-consumer:使用接口,也就是消费服务

要使用@DubboReference注解

  • 设置配置并关联注册中心
dubbo.registry.address=nacos://127.0.0.1:8848

本地启动了一个nacos服务端作为注册中心,端口号8848

其他配置就不截图了,可能涉密

  • 流程

服务A就是dubbo-consumer

服务B就是dubbo-provider

对于消费者来说,调用提供者的服务就像是本地调用一样(这是RPC的作用效果)。

Dubbo SPI介绍

Dubbo SPI的作用

比如,dubbo官方sdk提供了一个条件路由的功能,本公司认为,那个条件路由的功能不够强大,本公司还想加权重,想要改一下sdk,但是又不可能直接改源码,SPI能够支持本公司扩展功能,这就是SPI的作用。本公司的一组开发人员开基于SPI开发好了新的更强更适配公司的功能后,可以创建一个新的sdk,提供给其他程序员使用。

这个时候,有同学就会问了,那我直接写个实现类去实现不就好了,何必要引入一套复杂的SPI机制出来呢?对于这个问题,等你学完了Dubbo SPI的机制就能有自己的答案了。

再次强调:

对于普通的程序员,开发的功能是给广大人民群众用户使用的,比如开发一个逃宝平台来买卖物品。

对于想使用Dubbo SPI的程序员,开发的功能是给其他程序员使用,供他们开发更强大的程序来面向他们的用户。

什么是Dubbo SPI

SPI全称Service Provider Interface,是一种服务发现机制,Dubbo SPI 是SPI机制的一种实现。

简单来说就是,有人提供了一组接口,业务开发人员可以直接面向这套接口编程,但是有另一批人实现了这组接口,如何在程序运行期间自动找到那些实现类从而真正供开发人员使用呢?Dubbo SPI提供了一种开发实现类和找实现类的机制。在Dubbo SPI中,“找”也叫“加载”,“实现类”也叫“扩展”。

Java SPI和Dubbo SPI的对比

  1. 配置文件,前者是set集合,后者是Map(key value 结构)
  2. 前者就会实例化配置文件中所有实现类的对象,后者只实例化指定的实现类的对象
  3. 后者在创建对象时还加入了简单的依赖注入,包装器等功能
  4. 后者还加入了自适应扩展和激活扩展功能

这部分可以学完Java SPI和Dubbo SPI来看,便于深入了解二者。有对比,认识更深刻。推荐先学一下Java SPI机制,比Dubbo SPI简单很多,也可以对SPI有个初步了解。

开发扩展

看完加载扩展,就知道如何去开发扩展了

加载扩展

本部分十分硬核,涉及到dubbo源码,请在监护人陪同下观看。

加载策略(LoadingStrategies)

定义配置文件,文件名为接口名,文件内容是key-value结构,key是实现类名称(自定义别名),value是实现类的全类名

上面这个例子不好,不够具体。不如下面这个,直接实现(扩展)了Router类

加载策略三种(按优先级从高到低):

  1. META-INF/dubbo/internal/ (别名不能覆盖)
  2. META-INF/dubbo/(别名可以覆盖,后面的会覆盖前面的)
  3. META-INF/services/(别名可以覆盖,后面的会覆盖前面的)

底层还是用了Java SPI的ServiceLoader来获取所有的加载策略

@Test
public void testLoadingStrategies() {
    List<LoadingStrategy> loadingStrategies = ExtensionLoader.getLoadingStrategies();// 底层还是用了Java SPI的ServiceLoader来获取所有的加载策略
    loadingStrategies.forEach(strategy ->
            System.out.println(strategy.directory() + " " + strategy.getPriority()));
}

扩展加载器

  • 结构

简单理解:扩展加载器这个名字可以看出是用来加载扩展的,但它内部其实存储了一个扩展加载工厂,由这个扩展加载工厂去创建扩展,然后加载器再返回扩展。可以理解为在工厂模式上有封装了一层。

吐槽:dubbo的扩展工厂ExtensionFactory objectFactory 其实只保存了AdaptiveExtensionFactory,然后这个工厂再去调用具体的两个工厂dubbo SPI工厂和Spring工厂,使用了代理模式,但感觉有点设计过度了,这里的objectFactory完全可以指定具体的工厂dubbo SPI工厂或Spring工厂,没必要再套一层

public class ExtensionLoader<T> {
    // 加载器核心:一系列缓存
    // 扩展加载器的缓存 key是接口名,value是该接口的加载器
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);

    // 资源加载策略
    private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();
    
    // 接口
    private final Class<?> type;

    // 扩展工厂 用于获取扩展实例 有dubbo SPI工厂和Spring工厂
    private final ExtensionFactory objectFactory;


    // 🌟别名和实现类的关系🌟
    // 🌟以下两个数据结构刚好相反,从2个角度存储了实现类和别名
    // 实现类别名的缓存 key为实现类的class, value是实现类的别名
    // key = com.iqiyi.dubbo.rpc.cluster.route.trafficshift.TrafficShiftingRouterFactory
    // value = iqiyi-trafficshift-router
    private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();

    // 实现类的缓存 key为实现类的别名, value是实现类
    // key = iqiyi-trafficshift-router
    // value = com.iqiyi.dubbo.rpc.cluster.route.trafficshift.TrafficShiftingRouterFactory
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
    // 🌟别名和实现类的关系🌟
    
    // 🌟扩展和其真正的实例🌟
    // 🌟以下两个数据结构key分别是Class和别名,value都是实例对象
    // 扩展实例的缓存 key为实现类,value为实现类的实例
    // key = com.iqiyi.dubbo.rpc.cluster.route.trafficshift.TrafficShiftingRouterFactory
    // value = 实例对象
    private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
    
    // key = iqiyi-trafficshift-router
    // value = 实例对象
    // 实现类的实例的缓存 key为实现类的别名, value是实现类的实例
    private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    // 🌟扩展和其真正的实例🌟
    
    
    // 默认实现类的别名
    private String cachedDefaultName;
    
    // 包装类的缓存
    private Set<Class<?>> cachedWrapperClasses;
    
    
    
    // 激活扩展的缓存 key为别名 value为激活扩展类上的Activate注解
    // key = iqiyi-trafficshift-router
    // value = @Activate对象,这个比较特殊,要看后面的代码
    private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
    
    // 适配扩展实例的缓存 适配扩展只有一个
    private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
    
    // 适配扩展类的缓存
    private volatile Class<?> cachedAdaptiveClass = null;

}
  • 获取

getExtensionLoader

扩展加载器对外暴露的是自己存储了一个扩展加载工厂。

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {// 根据不同的接口类型去加载它的实现类(扩展),这里的type既可以传扩展,也可以传扩展工厂
    if (type == null) { 
         throw new IllegalArgumentException("Extension type == null");
    }
    // 必须是接口
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    }
    // 必须加上SPI注解
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type +
                                           ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }

    // 从缓存中获取扩展加载器ExtensionLoader
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {// 缓存里没有
        // 创建扩展加载器并放入缓存
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

private ExtensionLoader(Class<?> type) {
    // 封装接口类型
    this.type = type; 
    // 封装扩展工厂--利用扩展工厂创建实现类的对象
    objectFactory = (type == ExtensionFactory.class ? null : 
                      ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());// 备注:其实只会保存AdaptiveExtensionFactory这一种工厂,再由这一种工厂去遍历所有的工厂从而创建实现类(扩展)
}

扩展工厂

接口

@SPI
public interface ExtensionFactory {
    // 根据类型和别名获取扩展
    <T> T getExtension(Class<T> type, String name);
}

实现类

AdaptiveExtensionFactory
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    private final List<ExtensionFactory> factories;// 保存具体的真正创建扩展的扩展工厂

    /** 获取并存放所有的扩展工厂 */
    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);// 还是通过扩展加载器去获取扩展工厂
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    /** 遍历所有扩展工厂 如果工厂能获取到需要的扩展则返回 */
    @Override
    public <T> T getExtension(Class<T> type, String name) {// 本方法用于创建实现类(扩展)
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
}
SpiExtensionFactory

只针对加了SPI注解的接口

本质上还是使用ExtensionLoader去获取扩展(实现类),使用getAdaptiveExtension来获取适配的扩展!也就是说,这个工厂并没有发挥工厂的作用(创建对象),而是把功能向上给了Loader,这种设计不太好,工厂没有工厂的作用,Loader应该只作为一个加载对象的入口,而现在的Loader功能太多了,创建对象的功能也被它占了。

public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);// 还是要获取扩展加载器
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension(); // 由加载器去获取扩展,创建扩展的工作还是交给了加载器而不是工厂
            }
        }
        return null;
    }
}
SpringExtensionFactory

针对没有加SPI注解的接口 ,从spring容器获取扩展

public class SpringExtensionFactory implements ExtensionFactory {
    private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class);

    private static final Set<ApplicationContext> CONTEXTS = new ConcurrentHashSet<ApplicationContext>();

    public static void addApplicationContext(ApplicationContext context) {
        CONTEXTS.add(context);
        if (context instanceof ConfigurableApplicationContext) {
            ((ConfigurableApplicationContext) context).registerShutdownHook();
        }
    }

    public static void removeApplicationContext(ApplicationContext context) {
        CONTEXTS.remove(context);
    }

    public static Set<ApplicationContext> getContexts() {
        return CONTEXTS;
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {

        //SPI should be get from SpiExtensionFactory
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            return null;
        }

        for (ApplicationContext context : CONTEXTS) {
            T bean = BeanFactoryUtils.getOptionalBean(context, name, type);
            if (bean != null) {
                return bean;
            }
        }
        return null;
    }
}

普通扩展

普通扩展有3类,分别是默认扩展,注入扩展,包装扩展

获取方式

T extensionLoader.getExtension(String name);

@SPI // 必须加上@SPI注解
public interface DubboSPiService {
    void doSthInDubbo();
}

public class DubboSpiServiceImplA implements DubboSPiService{
    @Override
    public void doSthInDubbo() {
        System.out.println("doSthInDubbo ... A");
    }
}

public class DubboSpiServiceImplB implements DubboSPiService{
    @Override
    public void doSthInDubbo() {
        System.out.println("doSthInDubbo ... B");
    }
}

@Test
public void DubboSpiTest() {
    ExtensionLoader<DubboSPiService> loader = ExtensionLoader.getExtensionLoader(DubboSPiService.class);// 传接口的class
    DubboSPiService extension = loader.getExtension("aaa"); // 通过名称获取,如果传"true"字符串,会调用getDefaultExtension去直接获取默认扩展
    extension.doSthInDubbo();  
}
默认扩展

image-20231211102517985

@SPI("bbb") // 给定默认值的名称 不能给多个否则报错
public interface DubboSPiService {
    void doSthInDubbo();
}

public class DubboSpiServiceImplA implements DubboSPiService{
    @Override
    public void doSthInDubbo() {
        System.out.println("doSthInDubbo ... A");
    }
}

public class DubboSpiServiceImplB implements DubboSPiService{
    @Override
    public void doSthInDubbo() {
        System.out.println("doSthInDubbo ... B");
    }
}

@Test
public void DubboSpiDefaultTest() {
    // 先要获取ExtensionLoader
    ExtensionLoader<DubboSPiService> loader = ExtensionLoader.getExtensionLoader(DubboSPiService.class);
    // 再通过loader来获取扩展(实现类)
    DubboSPiService extension = loader.getExtension("true");//注意是传字符串的true
    extension.doSthInDubbo();
    DubboSPiService defaultExtension = loader.getDefaultExtension();//获取默认扩展的方法
    System.out.println(extension == defaultExtension);
}
注入扩展

实现类里要有接口属性,并且有对应的set方法—注入扩展是用set方法注入的

获取a扩展,则会自动将b扩展注入其中。

为什么要传a的名字,而不是b的名字,因为我们本来就是想获取a,b只是a使用的一个工具(也就是b被注入到a中的原因),我们其实是不知道b的。这里是a使用b。

@SPI
public interface InjectService {
    void inject();
}

public class InjectServiceImplA implements InjectService {

    private InjectService injectService;// 实现类里有接口的实例属性

    public void setInjectService(InjectService injectService) {// 给属性加set方法
        this.injectService = injectService;
    }

    @Override
    public void inject() {
        System.out.println("InjectServiceImplA");
    }
}

@Adaptive
public class InjectServiceImplB implements InjectService {

    @Override
    public void inject() {
        // 这里
        System.out.println("InjectServiceImplB");
    }
}

@Test
public void testInjectExtension() {
    ExtensionLoader<InjectService> loader = ExtensionLoader.getExtensionLoader(InjectService.class);
    InjectService extension = loader.getExtension("aaa");// dubbo会将bbb自动注入到aaa的属性中
    extension.inject();
}
包装扩展

实现类里要有接口属性,并且有对应构造方法—包装扩展使用构造方法包装的

实质上就是静态代理模式。WrapA就是WrapB的静态代理

思考:动态代理是什么?Java使用动态代理主要实现了哪些功能?

获取b扩展,则会将b扩展包装到a扩展中,最终实际获取到的是a扩展

为什么要传b的名字,而不是a的名字呢?因为其实我们并不知道有a,只是想获取b,但是a对其进行了包装,功能更强大,我们使用的时候还是以为使用的是b。这里是a包装b。

@SPI
public interface WrapService {
    void wrap();
}

public class WrapServiceImplA implements WrapService{

    private WrapService wrapService;// 实现类里有接口的实例属性

    public WrapServiceImplA(WrapService wrapService) {// 提供构造器,又包装了一层
        this.wrapService = wrapService;
    }

    @Override
    public void wrap() {
        // 这里正常的逻辑是调用b的wrap方法
        System.out.println("WrapServiceImplA");
    }
}

public class WrapServiceImplB implements WrapService{

    @Override
    public void wrap() {
        System.out.println("WrapServiceImplB");
    }
}

@Test
public void testWrapExtension() {
    ExtensionLoader<WrapService> loader = ExtensionLoader.getExtensionLoader(WrapService.class);
    WrapService extension = loader.getExtension("bbb");// 如果使用aaa,则会报错,因为当检测到这个类是包装类的时候,会自动跳过
    extension.wrap();// 获取到的是WrapServiceImplA对象,会自动将WrapServiceImplB注入进去
}
🌟源码🌟

根据名称获取扩展

ExtensionLoader.getExtension

ExtensionLoader.java

public T getExtension(String name) {
    return getExtension(name, true);// 默认采用包装扩展模式,也就是找b,默认给你包装一层a返回
}
public T getExtension(String name, boolean wrap) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    // 1.先判断是否获取 默认扩展
    if ("true".equals(name)) {
        return getDefaultExtension(); // 如果name是true 也会走defaultExtension
    }
    // 2.根据名称去缓存里获取扩展,没有则创建一个
    final Holder<Object> holder = getOrCreateHolder(name);
    Object instance = holder.get();
    // 经典创建对象的流程:先从缓存里找,缓存里没有就创建1个,然后存到缓存里
    // DLC 从缓存获取
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // 重点还是createExtension方法
                instance = createExtension(name, wrap); // 缓存没有就创建扩展
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

获取默认扩展

获取默认扩展其实是获取到了默认扩展名,然后走获取普通扩展的流程,这个方法不重要。

public T getDefaultExtension() {
    getExtensionClasses(); // 读取配置文件 加载各种缓存
    if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
        return null;
    }
    return getExtension(cachedDefaultName);// 找到了默认扩展的名字,可以走普通的获取扩展的流程来获取实现类
}

创建扩展🌟

思路:

1-加载扩展了的Class对象

2-从缓存EXTENSION_INSTANCES中找实现类

3-缓存没有,就用反射创建一个实现类实例,并存入缓存EXTENSION_INSTANCES

到这里就创建好一个简单的扩展,后面还有额外工作

4-注入扩展injectExtension(针对注入扩展的-setter方法)

5-对实现类做属性填充(针对包装扩展的-构造方法)

createExtension(String name, boolean wrap)

private T createExtension(String name, boolean wrap) {
    // 1.加载扩展类->获取扩展类对象 Map<name, class> key=别名, value=实现类class对象
    // 🌟cachedClasses缓存🌟
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        // 2.创建实现类
        // 先从EXTENSION_INSTANCES缓存里找 key=实现类class对象,value=实现类对象
        // 🌟EXTENSION_INSTANCES缓存🌟,这个缓存和cachedInstance类似,都存了实现类对象,只不过key是别名还是class,也就是说,一步还是两步的区别
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            // 缓存没有,就通过反射的newInstance创建一个对象,放到缓存里
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        
        // 3.对实例进行注入操作--注入扩展的逻辑
        injectExtension(instance); // 注入扩展,找setter方法
        
        // 4.包装扩展的逻辑
        // 处理包装类
        if (wrap) {// 不要忘了这个wrap是哪里来的  getExtension(String name, boolean wrap)
            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);// 含有Wrapper这个注解?这里没用过。
                    if (wrapper == null// 没有使用Wrapper注解,直接包装
                        || (ArrayUtils.contains(wrapper.matches(), name) // 使用了Wrapper要去判断一下能不能包装
                            && !ArrayUtils.contains(wrapper.mismatches(), name))) { 
                            // 将本instance注入到带有 构造器的新对象中,然后再执行注入扩展injectExtension的逻辑,如果不需要注入,那就不用了
           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);
    }
}

注入扩展🌟

复习一下:什么是注入扩展?

我们想获取aaa,那么,aaa要有setter方法,然后通过这个setter方法,将别的扩展,如bbb设置进入,完成对aaa扩展的注入。(源码的实现就是这一套思路)

ExtensionLoader.injectExtension

private T injectExtension(T instance) {

    if (objectFactory == null) {
        return instance;
    }

    try {
        // 利用反射找到setter方法,如果找不到,说明这个扩展不需要进行注入操作
        for (Method method : instance.getClass().getMethods()) {
            // 判断是否是setter方法
            if (!isSetter(method)) {
                continue;
            }
           
            // 如果方法有disableInject注解就跳过
            if (method.getAnnotation(DisableInject.class) != null) {
                continue;
            }
            // 获取到setter方法的第一个参数,也就是注入的接口类型
            Class<?> pt = method.getParameterTypes()[0];
            
            // 是否原生类-基本类型和包装类,Number,Date,排除掉其他的普通setter方法
            if (ReflectUtils.isPrimitives(pt)) {
                continue;
            }

            try {
                // 通过setter方法获取属性名 如setName返回name
                String property = getSetterProperty(method);
                // 通过工厂来获取扩展
                Object object = objectFactory.getExtension(pt, property);
                // 通过setter方法的反射把扩展注入
                if (object != null) {
                    method.invoke(instance, object);
                }
            } catch (Exception e) {
                logger.error("Failed to inject via method " + method.getName()
                             + " of interface " + type.getName() + ": " + e.getMessage(), e);
            }

        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

扩展名和类对象缓存的确定

getExtensionClasses

关键的一个中间方法,很多缓存都需要通过这个方法获取到扩展的信息。

其实就是对cachedClasses这个map缓存进行填充。

读取SPI配置文件,填充缓存并返回扩展名(别名)与扩展类对象的映射

private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();// 别名和扩展的映射保存在cachedClasses这个单层map中
    if (classes == null) {// 为空,说明还有没有获取
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                classes = loadExtensionClasses();// 关键方法,🌟loadExtensionClasses
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

加载扩展

本质上,加载缓存就是读取哪些配置文件。因此,可以思考一下,读取文件有哪些步骤呢?

loadExtensionClasses

重点。

private Map<String, Class<?>> loadExtensionClasses() {
    // 保存默认扩展的名称-默认扩展名就写在@SPI的属性中,这个找起来比较方便 
    cacheDefaultExtensionName();

    Map<String, Class<?>> extensionClasses = new HashMap<>();

    // 根据加载策略,读取不同目录下的配置文件,从而填充cachedClasses数据结构
    // 从不同的加载策略(目录)加载扩展类 关键方法:🌟loadDirectory
    for (LoadingStrategy strategy : strategies) {
        loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
    }

    return extensionClasses;
}

// 加载默认扩展
// 通过@SPI注解获取默认扩展名
private void cacheDefaultExtensionName() {
    // 获取SPI注解
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation == null) {
        return;
    }

    // 获取SPI注解的value
    String value = defaultAnnotation.value();
    // 如果SPI注解的value不为空
    if ((value = value.trim()).length() > 0) {
        // 逗号分割
        String[] names = NAME_SEPARATOR.split(value);
        // 超过一个默认实现报错
        if (names.length > 1) {
            throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
                                            + ": " + Arrays.toString(names));
        }
        // 默认实现只能有一个
        if (names.length == 1) {
            cachedDefaultName = names[0];
        }
    }
}

加载目录(寻找配置文件)

loadDirectory

这个是读取文件的第一步,先确定文件路径+文件名。但是要注意,最后表现出来的并不是普通的文件名,而是URL。

作用:拼接 配置文件的目录名+类名(全类名),通过classLoader加载并保存

这里使用了Java SPI来加载目录

本部分主要是拼接地址,确定配置文件位置,然后调用loadResource去解析配置文件。

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                               boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
    String fileName = dir + type; // 文件路径名 = 约定目录 + 接口全名
    try {
        Enumeration<java.net.URL> urls = null;
        ClassLoader classLoader = findClassLoader(); // 获取类加载器

        // 这个if没有讲,跳过
        // try to load from ExtensionLoader's ClassLoader first
        if (extensionLoaderClassLoaderFirst) {
            ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
            if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                urls = extensionLoaderClassLoader.getResources(fileName);
            }
        }

        // 根据文件路径名获取资源url
        if (urls == null || !urls.hasMoreElements()) {
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
        }

        if (urls != null) {
            while (urls.hasMoreElements()) {
                java.net.URL resourceURL = urls.nextElement();
                // 加载资源, 关键方法:🌟loadResource
                loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " +
                     type + ", description file: " + fileName + ").", t);
    }
}

加载资源(解析配置文件)

loadResource

这个是读取文件的第二步,知道了文件的位置,就可以正式读取文件,解析文件。

解析出具体的实现类,调用loadClass去加载它。

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
                              java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
        try {// 经典BufferedReader类,大家学习Java文件的时候应该遇到过
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
                String line;
                // 通过BufferedReader逐行读取(还是很经典),下面就不讲了,可以对着一个文件去看怎么处理字符串的
                while ((line = reader.readLine()) != null) {
                    // 跳过注释
                    final int ci = line.indexOf('#');
                    if (ci >= 0) {
                        line = line.substring(0, ci);
                    }
                    line = line.trim();
                    if (line.length() > 0) {
                        try {
                            String name = null;
                            // 等号分割  
                            int i = line.indexOf('=');
                            if (i > 0) {
                                name = line.substring(0, i).trim(); // 等号前是扩展的别名
                                line = line.substring(i + 1).trim(); // 等号后面是扩展的全类名
                            }
                            if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
                                // 加载类 关键方法:🌟loadClass
                                loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
                            }
                        } catch (Throwable t) {
                            IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }

加载类

loadClass

这个方法关键主要是因为它涉及到部分细节,并填充了部分缓存。功能上不算太关键的,核心就是根据类对象创建实例。

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name, boolean overridden) throws NoSuchMethodException {
    // 接口与实现类不存在实现关系则报错
    if (!type.isAssignableFrom(clazz)) {
        throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                                        type + ", class line: " + clazz.getName() + "), class "
                                        + clazz.getName() + " is not subtype of interface.");
    }
    
    // 🌟缓存适配扩展 -- cachedAdaptiveClass
    // 如果类有Adaptive注解 把当前类作为适配扩展类缓存起来 单独处理适配扩展
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        cacheAdaptiveClass(clazz, overridden);
    } 
    
    // 🌟缓存包装类 -- cachedWrapperClass
    // 如果类是包装类 把当前类作为包装类缓存起来
    else if (isWrapperClass(clazz)) {// 包装类的特点,有一个含有接口参数的构造器 
        cacheWrapperClass(clazz);
    } else {
        // 既不是适配扩展,也不是包装类的情况 也就是普通的实现类
        // 检查是否有无参构造
        clazz.getConstructor();
        
        // 如果配置文件没有给别名则name为空
        if (StringUtils.isEmpty(name)) {
            // 🌟小细节,配置文件没有配置别名怎么办?生成一个默认的名字-全小写类名形式
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
            }
        }

        // 拆分name为数组   为何要拆分? 因为可以这样配置xxx,yyy=实现类名称
        String[] names = NAME_SEPARATOR.split(name);
        if (ArrayUtils.isNotEmpty(names)) {
            // 🌟缓存激活扩展 cachedActivates
            // 缓存激活扩展类-找一下有没有@Active注解标注到扩展上
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                // 🌟缓存实现类与别名的映射
                cacheName(clazz, n); 
                // 放入extensionClasses-这是最终保存到缓存中的操作,这个就不讲了,就是一个存储
                saveInExtensionClass(extensionClasses, clazz, n, overridden);
            }
        }
    }
}

补充一个关于激活扩展的:cacheActivateClass

private void cacheActivateClass(Class<?> clazz, String name) {
    Activate activate = clazz.getAnnotation(Activate.class);// 获取这个类的注解
    if (activate != null) {
        cachedActivates.put(name, activate);// 能获取到就保存在cachedActivates这个map缓存中
    } else {
        // support com.alibaba.dubbo.common.extension.Activate
        com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(
            com.alibaba.dubbo.common.extension.Activate.class);
        if (oldActivate != null) {
            cachedActivates.put(name, oldActivate);
        }
    }
}

适配扩展(Adaptive extension)

也可以成为自适应扩展

装饰器模式。

作用

适配的作用就是动态找到一个扩展,刚好能满足我这个接口的需求(设置条件)。但是适配扩展的使用要求比较高,必须在接口设计之初就定义好以后的扩展要满足什么条件才能是合格的扩展,而且一次只能适配一个。如果是Router的话,那么可以有多种形式的路由方式,因此就不能用适配扩展了,可以使用后面的激活扩展。

使用

1)接口需加上**@SPI**注解

2)可以在接口的方法或者在实现类加上**@Adaptive**注解, 后者(适配类)优先级高于前值(适配方法)

3)如果是适配方法,那么该方法必须有URL参数或者拥有可以返回URL对象的无参方法的参数(不要求是第一个)

4)对于同一个接口,适配扩展只能有一个

5)通过**extensionLoader.getAdaptiveExtension()**方法获取

6)但是对于适配类和适配方法,后者用的更多,因为使用更方便,底层是动态获取扩展

适配类

适配类的使用比较简单,调用getAdaptiveExtension方法即可

@SPI
public interface HelloService {
    void sayHello();
}

@Adaptive// 适配扩展只能有1个
public class HelloServiceImplA implements HelloService{
    @Override
    public void sayHello() {
        System.out.println("HelloServiceImplA");
    }
}

public class HelloServiceImplB implements HelloService{
    @Override
    public void sayHello() {
        System.out.println("HelloServiceImplB");
    }
}
@Test
public void testAdaptiveExtension() {
  HelloService extension = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension();
  extension.sayHello();
}

适配方法

  • 用法一

@Adaptive的value: 字符串数组,URL参数的key值,

如果不写默认会生成一个,如接口名HelloService,默认是hello.service

@SPI
public interface HelloService {
    @Adaptive// 使用适配方法,@Adaptive注解要加在接口的方法上
    void sayHello(URL url);// 必须有URL参数
}

public class HelloServiceImplA implements HelloService{
    @Override
    public void sayHello(URL url) {
        System.out.println("HelloServiceImplA");
    }
}

public class HelloServiceImplB implements HelloService{
    @Override
    public void sayHello(URL url) {
        System.out.println("HelloServiceImplB");
    }
}

@Test
public void testAdaptiveExtension() {
  HelloService extension = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension();
  // 关键-使用
  URL url = URL.valueOf("cba://localhost/abc?hello.service=aaa");
  extension.sayHello(url);
}
  • 用法二🌟

@Adaptive的value给指定的值

@SPI
public interface HelloService {
    @Adaptive({"key1","key2"}) //有先后顺序,先按key1在URL找扩展名再按key2找
    void sayHello(URL url);
}

public class HelloServiceImplA implements HelloService{
    @Override
    public void sayHello(URL url) {
        System.out.println("HelloServiceImplA");
    }
}

public class HelloServiceImplB implements HelloService{
    @Override
    public void sayHello(URL url) {
        System.out.println("HelloServiceImplB");
    }
}

@Test
public void testAdaptiveExtension() {
    HelloService extension = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension();
    URL url = URL.valueOf("protocol://localhost/abc?key1=aaa&key2=bbb");// 先找key1,再找key2
    extension.sayHello(url);
}
  • 用法三

被加上@Adaptive的方法存在这样一个参数,该参数拥有可以返回URL对象的无参方法

@SPI
public interface HelloService {
    @Adaptive(value = {"key1","key2"})
    void sayHello(URLFactory service);
}

// 和用法二原理类似,不过参数不是直接的URL,而是可以返回URL的类
@FunctionalInterface
public interface URLFactory {
    URL getDubboURL();// URLFactory有可以返回URL的无参方法
}

public class HelloServiceImplA implements HelloService{

    @Override
    public void sayHello(URLFactory service) {
        System.out.println("HelloServiceImplA");
    }
}

public class HelloServiceImplB implements HelloService{
    @Override
    public void sayHello(URLFactory service) {
        System.out.println("HelloServiceImplB");
    }
}

@Test
public void testAdaptiveExtension() {
    ExtensionLoader<HelloService> loader = ExtensionLoader.getExtensionLoader(HelloService.class);
    HelloService extension = loader.getAdaptiveExtension();
    URL url = URL.valueOf("protocol://localhost/abc?key1=aaa&key2=bbb");
    extension.sayHello(() -> {return url;});// 匿名内部类实现
}
源码

获取适配扩展

getAdaptiveExtension→生成适配类的代码字符串→获取类加载器和编译器,并用编译器编译→返回适配扩展的实例对象

getAdaptiveExtension→createAdaptiveExtension→getAdaptiveExtensionClass

上面这一套逻辑和加载普通扩展还是比较像的。

public class ExtensionLoader<T> {
    // 获取适配扩展
    public T getAdaptiveExtension() {
        // 尝试从缓存中获取适配扩展
        Object instance = cachedAdaptiveInstance.get();
        // DLC保证只创建一次
        if (instance == null) {
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    // 拿不到的情况--createAdaptiveExtension
                    try {
                        // 缓存取不到 创建适配扩展
                        instance = createAdaptiveExtension();
                        // 创建后放入缓存
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        createAdaptiveInstanceError = t;
                        throw new IllegalStateException("Failed to create adaptive instance: " 
                                                        + t.toString(), t);
                    }
                }
            }
        }
        return (T) instance;
    }
    
    /** 创建适配扩展 */
    private T createAdaptiveExtension() {
        try {
            // 获取适配扩展类->反射创建对象->注入依赖
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ",
                                                cause: " + e.getMessage(), e);
        }
    }
                                 
    /** 获取适配扩展类 */                                     
    private Class<?> getAdaptiveExtensionClass() {
        // 加载所有的扩展类和对象到缓存中
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            // 适配类走此分支
            return cachedAdaptiveClass;
        }
        // 创建适配扩展类 适配方法走此分支
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
    
    /** 创建适配扩展类 */
    private Class<?> createAdaptiveExtensionClass() {
        // 生成class的字符串代码
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
        // 找一个类加载器
        ClassLoader classLoader = findClassLoader();
        // 找一个编译器
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        // 编译代码并加载类
        return compiler.compile(code, classLoader);
    }                                        
}

动态生成适配扩展

生成扩展的代码字符串🌟

public class AdaptiveClassCodeGenerator {
    public String generate() {
        // no need to generate adaptive class since there's no adaptive method found.
        if (!hasAdaptiveMethod()) {
            throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
        }

        StringBuilder code = new StringBuilder();
        code.append(generatePackageInfo()); // package
        code.append(generateImports());  // import
        code.append(generateClassDeclaration()); // class

        Method[] methods = type.getMethods(); 
        for (Method method : methods) {
            code.append(generateMethod(method)); // method
        }
        code.append("}");

        if (logger.isDebugEnabled()) {
            logger.debug(code.toString());
        }
        return code.toString();
    }
}

🌰2个生成出来的代码字符串例子

①入参带URL对象本身

package com.jdeng.dubbo.adaptive;

import org.apache.dubbo.common.extension.ExtensionLoader;

public class HelloService$Adaptive implements com.jdeng.dubbo.adaptive.HelloService {
    
  public void sayHello(org.apache.dubbo.common.URL arg0)  {
    if (arg0 == null) throw new IllegalArgumentException("url == null");
        
    org.apache.dubbo.common.URL url = arg0;
        
    // 获取适配扩展的别名extName
    String extName = url.getParameter("hello.service");// @Adaptive的参数指定了key=hello.service
        
    if(extName == null) throw new IllegalStateException("Failed to get extension       (com.jdeng.dubbo.adaptive.HelloService) name from url (" + url.toString() + ") use keys([hello.service])");
        // 使用获取普通扩展的方法去获取即可
        com.jdeng.dubbo.adaptive.HelloService extension = 
            (com.jdeng.dubbo.adaptive.HelloService)ExtensionLoader.getExtensionLoader
            (com.jdeng.dubbo.adaptive.HelloService.class).getExtension(extName);
        
    extension.sayHello(arg0);
  }
}

②入参带可以返回URL对象

package com.jdeng.dubbo.adaptive;
import org.apache.dubbo.common.extension.ExtensionLoader;

public class HelloService$Adaptive implements com.jdeng.dubbo.adaptive.HelloService {

    public void sayHello(com.jdeng.dubbo.adaptive.URLFactory arg0)  {
        if (arg0 == null)
            throw new IllegalArgumentException("com.jdeng.dubbo.adaptive.URLFactory argument == null");
        if (arg0.getDubboURL() == null) 
            throw new IllegalArgumentException("com.jdeng.dubbo.adaptive.URLFactory argument getDubboURL() == null");
        org.apache.dubbo.common.URL url = arg0.getDubboURL();
        String extName = url.getParameter("key1", url.getParameter("key2"));
        if(extName == null) throw new IllegalStateException("Failed to get extension (com.jdeng.dubbo.adaptive.HelloService) name from url (" + url.toString() + ") use keys([key1, key2])");
        com.jdeng.dubbo.adaptive.HelloService extension = (com.jdeng.dubbo.adaptive.HelloService)ExtensionLoader
            .getExtensionLoader(com.jdeng.dubbo.adaptive.HelloService.class).getExtension(extName);
        extension.sayHello(arg0);
    }
}
实战

适配类

1)ExtensionFactory

见笔记第四点

2)Compiler

@SPI("javassist")
public interface Compiler {
    Class<?> compile(String code, ClassLoader classLoader);
}

@Adaptive
public class AdaptiveCompiler implements Compiler {

    private static volatile String DEFAULT_COMPILER;

    public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }

    @Override
    public Class<?> compile(String code, ClassLoader classLoader) {
        Compiler compiler;
        ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
        String name = DEFAULT_COMPILER; // copy reference
        if (name != null && name.length() > 0) {
            compiler = loader.getExtension(name);
        } else {
            compiler = loader.getDefaultExtension(); //找default extension->javassist compiler
        }
        return compiler.compile(code, classLoader);
    }
}

适配方法

Protocol 接口

@SPI("dubbo")
public interface Protocol {

    int getDefaultPort();
    
    @Adaptive// 没有写key,默认key就是protocol,表示找有协议的url资源
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
   
    void destroy();

    default List<ProtocolServer> getServers() {
        return Collections.emptyList();
    }
}

动态生成的源码

没有加@Adaptive会直接throw exception

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    
    public void destroy()  {
        throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
    
    public int getDefaultPort()  {
        throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
    
    // 服务引用
    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg1;
        //为什么下面有"dubbo"呢?因为Protocol接口设置了默认扩展
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        // 获取到扩展
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        // 执行扩展的方法
        return extension.refer(arg0, arg1);
    }
    
    // 服务暴露
    public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
    
    public java.util.List getServers()  {
        throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
}

URL实例:

这个协议就是registry

registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=boot-server&dubbo=2.0.2&export=dubbo%3A%2F%2F169.254.231.58%3A20880%2Fcom.jdeng.api.GoodsService%3Fanyhost%3Dtrue%26application%3Dboot-server%26bind.ip%3D169.254.231.58%26bind.port%3D20880%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26hash.arguments%3D0%2C1%26hash.nodes%3D320%26interface%3Dcom.jdeng.api.GoodsService%26metadata-type%3Dremote%26methods%3DinsertGoods%2CselectGoods%2CgetGoods%26pid%3D13692%26qos.enable%3Dfalse%26release%3D2.7.8%26selectGoods.loadbalance%3Dconsistenthash%26selectGoods.return%3Dtrue%26serialization%3Dhessian2%26side%3Dprovider%26timeout%3D4000%26timestamp%3D1663598039636%26warmup%3D0&pid=13692&qos.enable=false&registry=zookeeper&release=2.7.8&timestamp=1663597963467
registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=boot-server&dubbo=2.0.2&export=dubbo://169.254.231.58:20880/com.jdeng.api.GoodsService?anyhost=true&application=boot-server&bind.ip=169.254.231.58&bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&hash.arguments=0,1&hash.nodes=320&interface=com.jdeng.api.GoodsService&metadata-type=remote&methods=insertGoods,selectGoods,getGoods&pid=13692&qos.enable=false&release=2.7.8&selectGoods.loadbalance=consistenthash&selectGoods.return=true&serialization=hessian2&side=provider&timeout=4000&timestamp=1663598039636&warmup=0&pid=13692&qos.enable=false&registry=zookeeper&release=2.7.8&timestamp=1663597963467

思考

关于Adaptive Extension, 为什么要设计两种用法(在实现类上加@Adaptive注解和接口方法上加@Adaptive注解)?

适配类是给程序员自定义服务发现(选择扩展)的逻辑

适配方法是dubbo提供给程序员的内置的服务发现(选择扩展)的逻辑. 这个用的多,而适配类用的少。

激活扩展(Activate extension)

作用

根据group和value,可以更加方便的选择扩展,激活扩展更加灵活。也可以不设置其他参数,只设置order,这样的话就会直接激活扩展。爱奇艺用的就是这种。

dubbo官方的StateRouterFactory采用的是适配扩展,其众多的实现类中,除了FileStateRouterFactory和ConditionStateRouter和ScriptStateRouterFactory,其他用的都是激活扩展。除了接口,有11个真正的扩展。

最后运行出来的有8个,刚好不包括那3个

使用

1)接口需加上@SPI注解

2)实现类上加上@Activate注解(只能实现类上加)

group 分组 常用producer和consumer

value 激活条件

order 对扩展进行排序 数值越小排名越前

3)对于同一个接口,激活扩展可以有多个

4)通过extensionLoader.getActivateExtension(URL url, String key, String group)获取,返回的是激活扩展的List集合

分组和排序

extensionLoader.getActivateExtension(URL url, String key, String group) 中group可以不传。

@SPI
public interface ActiveService {
    void doSth();
}

@Activate(group = {"active-group"}, order = 1)
public class ActiveServiceImplA implements ActiveService{

    @Override
    public void doSth() {
        System.out.println("call ActiveServiceImpl A");
    }
}

@Activate(group = {"active-group"}, order = 2)
public class ActiveServiceImplB implements ActiveService{
    @Override
    public void doSth() {
        System.out.println("call ActiveServiceImpl B");
    }
}

@Activate(group = {"nonactive-group"}, order = 3)
public class ActiveServiceImplC implements ActiveService{

    @Override
    public void doSth() {
        System.out.println("call ActiveServiceImpl C");
    }
}

/** 测试分组和排序 */
@Test
public void testActiveExtensionGroup() {
    URL url = URL.valueOf("protocol://localhost");
    ExtensionLoader<ActiveService> loader = ExtensionLoader.getExtensionLoader(ActiveService.class);
    List<ActiveService> extensionList = loader.getActivateExtension(url,"","active-group");
    extensionList.forEach(x -> x.doSth());
}

激活条件

激活条件要看@Activate中的value,可以写成"k:v"形式,也可以写成只有key的形式:“k”。还可以不设置这个字段,则表示默认情况下直接命中value。

激活条件有2种判断方式

  1. loader.getActivateExtension(URL url, String key, String group)中的key字段为空字符串,表示这里不传入key。则解析URL中的key。

    URL中的key和@Activate中设置的key相同,或者URL中的key是xxx.key的形式。

    URL : cache=redis @Activate(value={“cache=xxx”})

    URL : xxx.cache=redis @Activate(value={“cache=XXXXXXX”})

    另外,对value也有要求。

    如果激活条件中的value不为空,则URL的value必须匹配

    URL : cache=redis @Activate(value={“cache=redis”})

    如果激活条件的value为空,则URL必须有value,不能只有1个key

    URL : cache=redis @Activate(value={“cache”})

    总结:

    如果激活条件中设置了key和value,URL就要对应匹配上;如果激活条件中只设置了key,URL中只要key匹配就可以命中扩展,value可以任意,但不能缺。

  2. loader.getActivateExtension(URL url, String key, String group)中直接传入key

    除了按照上面的规则匹配,还会将value作为扩展的别名再去找(这里应该就是传key和不传key的区别)

    如果在value前加 - ,可以排除这种扩展,比如URL url = URL.valueOf(“protocol://localhost?k=-ccc”);排除了k=ccc的扩展。

@SPI
public interface ActiveService {
    void doSth();
}

@Activate(group = {"active-group"}, value = {"k:xxx"}, order = 1)
public class ActiveServiceImplA implements ActiveService{

    @Override
    public void doSth() {
        System.out.println("call ActiveServiceImpl A");
    }
}

@Activate(group = {"active-group"}, value = {"k"}, order = 2)
public class ActiveServiceImplB implements ActiveService{
    @Override
    public void doSth() {
        System.out.println("call ActiveServiceImpl B");
    }
}

@Activate(group = {"nonactive-group"}, order = 3)
public class ActiveServiceImplC implements ActiveService{

    @Override
    public void doSth() {
        System.out.println("call ActiveServiceImpl C");
    }
}

@Test
public void testActiveExtensionValueWithoutKey() {
    URL url = URL.valueOf("protocol://localhost?k=aaa");
    ExtensionLoader<ActiveService> loader = ExtensionLoader.getExtensionLoader(ActiveService.class);
    List<ActiveService> extensionList = loader.getActivateExtension(url,"");// 不传key
    extensionList.forEach(x -> x.doSth());
}

@Test
public void testActiveExtensionValueWithKey() {
    URL url = URL.valueOf("protocol://localhost?k=aaa");
    ExtensionLoader<ActiveService> loader = ExtensionLoader.getExtensionLoader(ActiveService.class);
    List<ActiveService> extensionList = loader.getActivateExtension(url,"k");// 传key
    extensionList.forEach(x -> x.doSth());
}

@Test
public void testActiveExtensionValueWithKeyExcludeExtension() {
    URL url = URL.valueOf("protocol://localhost?k=-ccc");
    ExtensionLoader<ActiveService> loader = ExtensionLoader.getExtensionLoader(ActiveService.class);
    List<ActiveService> extensionList = loader.getActivateExtension(url,"k");// 传key
    extensionList.forEach(x -> x.doSth());
}
源码

getActivateExtension是个重载方法,也可以没有group参数。

public List<T> getActivateExtension(URL url, String key, String group) {
    String value = url.getParameter(key); // 根据传入的key获取url的values
    return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
}

// key的作用结束,直接使用values
public List<T> getActivateExtension(URL url, String[] values, String group) {
    List<T> activateExtensions = new ArrayList<>();
    // 将value数组转变为扩展的别名
    List<String> names = values == null ? new ArrayList<>(0) : asList(values);
    
    // 根据Group和values过滤出匹配的扩展 根据order排序
    if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
        // 加载扩展类
        getExtensionClasses();
        // 🌟激活扩展的缓存 key为扩展别名 value为@Activate注解--注意,这里value是注解,比较特殊
        for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
            String name = entry.getKey();// 这里的name就是所有激活扩展的别名
            Object activate = entry.getValue();
            String[] activateGroup, activateValue;
            // 获取注解上的group和value
            if (activate instanceof Activate) {
                activateGroup = ((Activate) activate).group();
                activateValue = ((Activate) activate).value();
            } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
                activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
                activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
            } else {
                continue;
            }
            // 匹配规则的代码实现:结合上面的激活条件一起看
            if (isMatchGroup(group, activateGroup) // 根据group进行匹配
                && !names.contains(name)// 如果value刚好是激活扩展的别名,那就走普通获取扩展的代码,不走获取激活扩展的代码
                && !names.contains(REMOVE_VALUE_PREFIX + name)// 这个就是排除的那种情况
                && isActive(activateValue, url)) { // 根据value进行匹配
                activateExtensions.add(getExtension(name));
            }
        }
        // 根据order进行排序 数值越小排名越前
        activateExtensions.sort(ActivateComparator.COMPARATOR);
    }
    
    // 如果传入了别名 那么根据别名获取扩展--还是之前的根据别名获取扩展的机制
    List<T> loadedExtensions = new ArrayList<>();
    for (int i = 0; i < names.size(); i++) {
        String name = names.get(i);
        if (!name.startsWith(REMOVE_VALUE_PREFIX)
            && !names.contains(REMOVE_VALUE_PREFIX + name)) {
            if (DEFAULT_KEY.equals(name)) {
                if (!loadedExtensions.isEmpty()) {
                    activateExtensions.addAll(0, loadedExtensions);
                    loadedExtensions.clear();
                }
            } else {
                loadedExtensions.add(getExtension(name));
            }
        }
    }
    if (!loadedExtensions.isEmpty()) {
        activateExtensions.addAll(loadedExtensions);
    }
    return activateExtensions;
}

private boolean isMatchGroup(String group, String[] groups) {
    if (StringUtils.isEmpty(group)) {
        return true;
    }
    // 只要命中其中一个group即可
    if (groups != null && groups.length > 0) {
        for (String g : groups) {
            if (group.equals(g)) {
                return true;
            }
        }
    }
    return false;
}

private boolean isActive(String[] keys, URL url) {
    if (keys.length == 0) {// keys是指@Activate注解上传的value字段,也就是那些key:value,key,key:value
        return true;
    }
    for (String key : keys) {
        
        String keyValue = null;
        // 获取@Activate注解上的key, value
        // @Activate注解存在k:v的情况
        if (key.contains(":")) {
            String[] arr = key.split(":");
            key = arr[0];
            keyValue = arr[1];
        }
        // 获取URL上的key, value
        for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
            String k = entry.getKey();
            String v = entry.getValue();
            // k,v变量 -> url
            // key, keyValue变量 -> 注解
            // 注意!下面这个判断就是之前复杂的激活条件的实现!
            if ((k.equals(key) || k.endsWith("." + key))
                && ((keyValue != null && keyValue.equals(v)) || (keyValue == null && ConfigUtils.isNotEmpty(v)))) {
                return true;
            }
        }
    }
    return false;
}
实战

Filter接口

@SPI
public interface Filter {
    Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
}

@Activate(group = CONSUMER, value = ACTIVES_KEY)// 消费者才会激活,ACTIVES_KEY=actives
public class ActiveLimitFilter implements Filter, Filter.Listener {
}

@Activate(group = CommonConstants.PROVIDER)// 服务提供者才会激活
public class TimeoutFilter implements Filter, Filter.Listener {
}

此类负责装配调用链(invoker + filters)

public class ProtocolFilterWrapper implements Protocol {

    private final Protocol protocol;

    public ProtocolFilterWrapper(Protocol protocol) {
        if (protocol == null) {
            throw new IllegalArgumentException("protocol == null");
        }
        this.protocol = protocol;
    }

    private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);

        if (!filters.isEmpty()) {
            for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;
                last = new Invoker<T>() {
                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                        Result asyncResult;
                        try {
                            asyncResult = filter.invoke(next, invocation);
                        } catch (Exception e) {
                            if (filter instanceof ListenableFilter) {
                                ListenableFilter listenableFilter = ((ListenableFilter) filter);
                                try {
                                    Filter.Listener listener = listenableFilter.listener(invocation);
                                    if (listener != null) {
                                        listener.onError(e, invoker, invocation);
                                    }
                                } finally {
                                    listenableFilter.removeListener(invocation);
                                }
                            } else if (filter instanceof Filter.Listener) {
                                Filter.Listener listener = (Filter.Listener) filter;
                                listener.onError(e, invoker, invocation);
                            }
                            throw e;
                        } finally {

                        }
                        return asyncResult.whenCompleteWithContext((r, t) -> {
                            // .....
                        });
                    }

                };
            }
        }

        return last;
    }

    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        if (UrlUtils.isRegistry(invoker.getUrl())) {
            return protocol.export(invoker);
        }
        return protocol.export(buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
    }

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (UrlUtils.isRegistry(url)) {
            return protocol.refer(type, url);
        }
        return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
    }

}

更佳实践RouterFactory接口

思考

为什么dubbo要设计适配扩展和激活扩展?

SPI要解决的核心问题是发现服务,就是接口存在多个实现的情况下如何灵活地选择。

1)以别名获取扩展实现 – 最基础的方式

2)获取默认扩展 – 兜底的方式

3)通过适配扩展获取真正需要的扩展 – 适配扩展的本质就是封装选择扩展的逻辑

4)通过激活注解获取需要的扩展 – 更加灵活的方式,而且可以选择多个扩展

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值