• Dubbo源码学习(九) 详解Dubbo里的SPI


    目录

    一、SPI是什么?

    二、SPI的具体使用

    Services里的SPI

    Dubbo.internal里的SPI

    三、@SPI注解使用

    四、小结


    一、SPI是什么?

            SPI是中间件设计不可缺少的一项,SPI能提高应用的可插拔性,SPI的英文全称: Service Provider Implementation, 我们可以在META-INF目录里配置SPI, ServiceLoader会根据配置来找到接口的实现,那Dubbo里的@SPI又是什么呢?

            Dubbo的Common模块里,定义了一个@SPI注解,该注解在extension包里,作用是标记一个接口为可扩展性的接口,具体的用法在下文里演示,被@SPI标记的接口一般是多扩展多实现的,默认的作用范围ExtensionScope是应用级别的。

    1. /*
    2. * Licensed to the Apache Software Foundation (ASF) under one or more
    3. * contributor license agreements. See the NOTICE file distributed with
    4. * this work for additional information regarding copyright ownership.
    5. * The ASF licenses this file to You under the Apache License, Version 2.0
    6. * (the "License"); you may not use this file except in compliance with
    7. * the License. You may obtain a copy of the License at
    8. *
    9. * http://www.apache.org/licenses/LICENSE-2.0
    10. *
    11. * Unless required by applicable law or agreed to in writing, software
    12. * distributed under the License is distributed on an "AS IS" BASIS,
    13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14. * See the License for the specific language governing permissions and
    15. * limitations under the License.
    16. */
    17. package org.apache.dubbo.common.extension;
    18. import java.lang.annotation.Documented;
    19. import java.lang.annotation.ElementType;
    20. import java.lang.annotation.Retention;
    21. import java.lang.annotation.RetentionPolicy;
    22. import java.lang.annotation.Target;
    23. /**
    24. * Marker for extension interface
    25. *

    26. * Changes on extension configuration file
    27. * Use Protocol as an example, its configuration file 'META-INF/dubbo/com.xxx.Protocol' is changed from:
    28. *
    29. * com.foo.XxxProtocol
    30. * com.foo.YyyProtocol
    31. *
  • *

  • * to key-value pair
  • *
  • * xxx=com.foo.XxxProtocol
  • * yyy=com.foo.YyyProtocol
  • *
  • *
  • * The reason for this change is:
  • *

  • * If there's third party library referenced by static field or by method in extension implementation, its class will
  • * fail to initialize if the third party library doesn't exist. In this case, dubbo cannot figure out extension's id
  • * therefore cannot be able to map the exception information with the extension, if the previous format is used.
  • *

  • * For example:
  • *

  • * Fails to load Extension("mina"). When user configure to use mina, dubbo will complain the extension cannot be loaded,
  • * instead of reporting which extract extension implementation fails and the extract reason.
  • *

  • */
  • @Documented
  • @Retention(RetentionPolicy.RUNTIME)
  • @Target({ElementType.TYPE})
  • public @interface SPI {
  • /**
  • * default extension name
  • */
  • String value() default "";
  • /**
  • * scope of SPI, default value is application scope.
  • */
  • ExtensionScope scope() default ExtensionScope.APPLICATION;
  • }
  •         @SPI注解里包含2个属性:value和scope, scope 能指定接口的生效范围,包含4个值,分别为: FRAMEWORK、APPLICATION、MODULE和SELF。 

    1. public enum ExtensionScope {
    2. /**
    3. * The extension instance is used within framework, shared with all applications and modules.
    4. *
    5. *

      Framework scope SPI extension can only obtain {@link FrameworkModel},

    6. * cannot get the {@link ApplicationModel} and {@link ModuleModel}.

    7. *
    8. *

    9. * Consideration:
    10. *
      1. *
      2. Some SPI need share data between applications inside framework
  • *
  • Stateless SPI is safe shared inside framework
  • *
  • */
  • FRAMEWORK,
  • /**
  • * The extension instance is used within one application, shared with all modules of the application,
  • * and different applications create different extension instances.
  • *
  • *

    Application scope SPI extension can obtain {@link FrameworkModel} and {@link ApplicationModel},

  • * cannot get the {@link ModuleModel}.

  • *
  • *

  • * Consideration:
  • *
    1. *
    2. Isolate extension data in different applications inside framework
    3. *
    4. Share extension data between all modules inside application
    5. *
    6. */
    7. APPLICATION,
    8. /**
    9. * The extension instance is used within one module, and different modules create different extension instances.
    10. *
    11. *

      Module scope SPI extension can obtain {@link FrameworkModel}, {@link ApplicationModel} and {@link ModuleModel}.

    12. *
    13. *

    14. * Consideration:
    15. *
      1. *
      2. Isolate extension data in different modules inside application
      3. *
      4. */
      5. MODULE,
      6. /**
      7. * self-sufficient, creates an instance for per scope, for special SPI extension, like {@link ExtensionInjector}
      8. */
      9. SELF
      10. }
      11.          扩展接口共享范围:  FRAMEWORK> APPLICATION>MODULE>SELF。

        二、SPI的具体使用

                在common模块里,我们可以发现/resources/META-INF目录下包含2个目录:

        1. META-INF
        2. ├─ dubbo
        3. │ └─ internal
        4. │ ├─ org.apache.dubbo.common.compiler.Compiler
        5. │ ├─ org.apache.dubbo.common.config.configcenter.DynamicConfigurationFactory
        6. │ ├─ org.apache.dubbo.common.context.ApplicationExt
        7. │ ├─ org.apache.dubbo.common.context.ModuleExt
        8. │ ├─ org.apache.dubbo.common.convert.Converter
        9. │ ├─ org.apache.dubbo.common.convert.multiple.MultiValueConverter
        10. │ ├─ org.apache.dubbo.common.extension.ExtensionInjector
        11. │ ├─ org.apache.dubbo.common.infra.InfraAdapter
        12. │ ├─ org.apache.dubbo.common.logger.LoggerAdapter
        13. │ ├─ org.apache.dubbo.common.status.StatusChecker
        14. │ ├─ org.apache.dubbo.common.store.DataStore
        15. │ ├─ org.apache.dubbo.common.threadpool.ThreadPool
        16. │ ├─ org.apache.dubbo.common.threadpool.manager.ExecutorRepository
        17. │ ├─ org.apache.dubbo.common.url.component.param.DynamicParamSource
        18. │ ├─ org.apache.dubbo.metadata.definition.builder.TypeBuilder
        19. │ ├─ org.apache.dubbo.rpc.model.BuiltinServiceDetector
        20. │ └─ org.apache.dubbo.rpc.model.ScopeModelInitializer
        21. └─ services
        22. └─ org.apache.dubbo.common.extension.LoadingStrategy

        Services里的SPI

                先看services目录,这是java的serviceLoader能加载的目录

         通过ServiceLoader 加载所有在service目录下配置的接口的所有实现。

        1. import static java.util.ServiceLoader.load;
        2. import static java.util.stream.StreamSupport.stream;
        3. private static LoadingStrategy[] loadLoadingStrategies() {
        4. LoadingStrategy[] strategies = stream(load(LoadingStrategy.class).spliterator(), false)
        5. .sorted()
        6. .toArray(LoadingStrategy[]::new);
        7. System.out.println();
        8. return strategies;
        9. }

         此方法能加载resources/META-INF/services/目录下的所有接口实现,包含tests里的。

         对应的实现写在文件里:

        看下loadingStrategy接口实现:

        打印结果为配置的3种+ Tests实现的一种=4种。

        接着看Dubbo目录下配置的SPI案例

        Dubbo.internal里的SPI

                dubbo.internal里配置的SPI是通过extensionLoader的loadDirectoryInternal的 DubboInternalLoadingStrategy 来加载的

        1. public class DubboInternalLoadingStrategy implements LoadingStrategy {
        2. @Override
        3. public String directory() {
        4. return "META-INF/dubbo/internal/";
        5. }
        6. @Override
        7. public int getPriority() {
        8. return MAX_PRIORITY;
        9. }
        10. @Override
        11. public String getName() {
        12. return "DUBBO_INTERNAL";
        13. }
        14. }

                看个例子,Dubbo可以在启动的时候可以选择使用注册中心来提供服务注册和发现的功能,使用zookeeper作为注册中心需要借助dubbo-registry-zookeeper模块实现服务的注册与发现,dubbo 借助SPI 实现该模块的装载。

        1. <dependency>
        2. <groupId>org.apache.dubbogroupId>
        3. <artifactId>dubbo-registry-zookeeperartifactId>
        4. dependency>

                在我配置N/A时, 不会将该服务注册到zk上,配置zk的地址时才会将service注册到zk上,这里也注册和服务发现使用到了SPI。

                首先将provider的application.yml里zk配置修改为N/A:

        1. dubbo:
        2. application:
        3. name: dubbo-springboot-demo-provider
        4. protocol:
        5. name: dubbo
        6. port: -1
        7. registry:
        8. id: zk-registry
        9. address: N/A
        10. config-center:
        11. address: N/A
        12. # metadata-report:
        13. # address: N/A

                我们可以发现在启动的时候并未经过ZookeeperRegistryFactory。

                 接着修改application.yml的zk配置为:zookeeper://127.0.0.1:2181

        1. dubbo:
        2. application:
        3. name: dubbo-springboot-demo-provider
        4. protocol:
        5. name: dubbo
        6. port: -1
        7. registry:
        8. id: zk-registry
        9. address: zookeeper://127.0.0.1:2181
        10. config-center:
        11. address: zookeeper://127.0.0.1:2181
        12. metadata-report:
        13. address: zookeeper://127.0.0.1:2181

                启动zk server, 然后启动provider。 

                由图可知,在应用启动的时候成功进入到ZookeeperRegistryFactory。也就是说在配置ZK有效IP地址的情况下,我们在dubbo.intenal里配置SPI的实现类ZookeeperRegistryFactory得到了装载,从这里可以发现我们可以借助SPI实现模块的可插拔

                写到这里,@SPI的注解功能也慢慢的拉开了序幕,接下来演示@SPI注解的作用。

        三、@SPI注解使用

                看Protocol接口,该接口的实现有很多个,是dubbo比较常用的SPI,他用到了@SPI的scope功能,为了更好的演示@SPI注解带来的效果,修改dubbo-rpc模块里的Protocol的@SPI注解里的scope属性为SELF或者MODULE。

        @SPI(value = "dubbo", scope = ExtensionScope.SELF)
        

        SELF表示只能在本SCOPE使用,外部模块引用也不生效。@SPI(scope = SELF),启动一个Provider, 在ConfigurableMetadataServiceExporter.generateMetadataProtocol()打一个断点,如下图:

                观察结果,可以发现无法获取到ExtensionLoader, 因为不在同一个模块里,重新修改SCOPE值为FRAMEWORK。

                provider 启动成功,ExtensionLoader也能获取到了。

                虽然Protocol在META/INF目录下配置了SPI,我修改了SCOPE后,其他模块引用该接口时,由于不在一个模块里,对应的接口实现就没有生效。

        四、小结

                Dubbo的@spi注解可以说是SPI的一个增强扩展,能让SPI 适用于指定的范围下, ExtensionScope 枚举了4个范围,默认为application范围内可使用。

      12. 相关阅读:
        代码随想录算法训练营第二十七天| 131.分割回文串
        【无标题】
        如何通过JoinQuant实现一个最简单的策略?
        TorchSemiSeg项目调试
        68 - 令人迷惑的写法
        本周大新闻|索尼PS VR2体验首次公开;Meta Quest Pro开箱视频曝光
        c# --- 抽象类,密封类与子类的构造函数
        C++ static_cast、dynamic_cast、const_cast 和 reinterpret_cast 用处和区别
        数据结构与算法-腾讯面试题单向链表的翻转
        ROS-Ubuntu 版本相关
      13. 原文地址:https://blog.csdn.net/qq_33036061/article/details/127632427