目录
SPI是中间件设计不可缺少的一项,SPI能提高应用的可插拔性,SPI的英文全称: Service Provider Implementation, 我们可以在META-INF目录里配置SPI, ServiceLoader会根据配置来找到接口的实现,那Dubbo里的@SPI又是什么呢?
Dubbo的Common模块里,定义了一个@SPI注解,该注解在extension包里,作用是标记一个接口为可扩展性的接口,具体的用法在下文里演示,被@SPI标记的接口一般是多扩展多实现的,默认的作用范围ExtensionScope是应用级别的。
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- package org.apache.dubbo.common.extension;
-
- import java.lang.annotation.Documented;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- /**
- * Marker for extension interface
- *
- * Changes on extension configuration file
- * Use
Protocol as an example, its configuration file 'META-INF/dubbo/com.xxx.Protocol' is changed from:
- *
- * com.foo.XxxProtocol
- * com.foo.YyyProtocol
- *
- *
- * 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。
- public enum ExtensionScope {
-
- /**
- * The extension instance is used within framework, shared with all applications and modules.
- *
- *
Framework scope SPI extension can only obtain {@link FrameworkModel},
- * cannot get the {@link ApplicationModel} and {@link ModuleModel}.
- *
- *
- * Consideration:
- *
- *
- 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:
- *
- *
- Isolate extension data in different applications inside framework
- *
- Share extension data between all modules inside application
- *
- */
- APPLICATION,
-
- /**
- * The extension instance is used within one module, and different modules create different extension instances.
- *
- *
Module scope SPI extension can obtain {@link FrameworkModel}, {@link ApplicationModel} and {@link ModuleModel}.
- *
- *
- * Consideration:
- *
- *
- Isolate extension data in different modules inside application
- *
- */
- MODULE,
-
- /**
- * self-sufficient, creates an instance for per scope, for special SPI extension, like {@link ExtensionInjector}
- */
- SELF
- }
扩展接口共享范围: FRAMEWORK> APPLICATION>MODULE>SELF。
在common模块里,我们可以发现/resources/META-INF目录下包含2个目录:
- META-INF
- ├─ dubbo
- │ └─ internal
- │ ├─ org.apache.dubbo.common.compiler.Compiler
- │ ├─ org.apache.dubbo.common.config.configcenter.DynamicConfigurationFactory
- │ ├─ org.apache.dubbo.common.context.ApplicationExt
- │ ├─ org.apache.dubbo.common.context.ModuleExt
- │ ├─ org.apache.dubbo.common.convert.Converter
- │ ├─ org.apache.dubbo.common.convert.multiple.MultiValueConverter
- │ ├─ org.apache.dubbo.common.extension.ExtensionInjector
- │ ├─ org.apache.dubbo.common.infra.InfraAdapter
- │ ├─ org.apache.dubbo.common.logger.LoggerAdapter
- │ ├─ org.apache.dubbo.common.status.StatusChecker
- │ ├─ org.apache.dubbo.common.store.DataStore
- │ ├─ org.apache.dubbo.common.threadpool.ThreadPool
- │ ├─ org.apache.dubbo.common.threadpool.manager.ExecutorRepository
- │ ├─ org.apache.dubbo.common.url.component.param.DynamicParamSource
- │ ├─ org.apache.dubbo.metadata.definition.builder.TypeBuilder
- │ ├─ org.apache.dubbo.rpc.model.BuiltinServiceDetector
- │ └─ org.apache.dubbo.rpc.model.ScopeModelInitializer
- └─ services
- └─ org.apache.dubbo.common.extension.LoadingStrategy
先看services目录,这是java的serviceLoader能加载的目录

通过ServiceLoader 加载所有在service目录下配置的接口的所有实现。
- import static java.util.ServiceLoader.load;
- import static java.util.stream.StreamSupport.stream;
-
-
-
-
- private static LoadingStrategy[] loadLoadingStrategies() {
- LoadingStrategy[] strategies = stream(load(LoadingStrategy.class).spliterator(), false)
- .sorted()
- .toArray(LoadingStrategy[]::new);
-
- System.out.println();
- return strategies;
- }
此方法能加载resources/META-INF/services/目录下的所有接口实现,包含tests里的。

对应的实现写在文件里:
看下loadingStrategy接口实现:
打印结果为配置的3种+ Tests实现的一种=4种。

接着看Dubbo目录下配置的SPI案例
dubbo.internal里配置的SPI是通过extensionLoader的loadDirectoryInternal的 DubboInternalLoadingStrategy 来加载的
- public class DubboInternalLoadingStrategy implements LoadingStrategy {
-
- @Override
- public String directory() {
- return "META-INF/dubbo/internal/";
- }
-
- @Override
- public int getPriority() {
- return MAX_PRIORITY;
- }
-
- @Override
- public String getName() {
- return "DUBBO_INTERNAL";
- }
- }
看个例子,Dubbo可以在启动的时候可以选择使用注册中心来提供服务注册和发现的功能,使用zookeeper作为注册中心需要借助dubbo-registry-zookeeper模块实现服务的注册与发现,dubbo 借助SPI 实现该模块的装载。
- <dependency>
- <groupId>org.apache.dubbogroupId>
- <artifactId>dubbo-registry-zookeeperartifactId>
- dependency>
在我配置N/A时, 不会将该服务注册到zk上,配置zk的地址时才会将service注册到zk上,这里也注册和服务发现使用到了SPI。

首先将provider的application.yml里zk配置修改为N/A:
- dubbo:
- application:
- name: dubbo-springboot-demo-provider
- protocol:
- name: dubbo
- port: -1
- registry:
- id: zk-registry
- address: N/A
- config-center:
- address: N/A
- # metadata-report:
- # address: N/A
我们可以发现在启动的时候并未经过ZookeeperRegistryFactory。

接着修改application.yml的zk配置为:zookeeper://127.0.0.1:2181
- dubbo:
- application:
- name: dubbo-springboot-demo-provider
- protocol:
- name: dubbo
- port: -1
- registry:
- id: zk-registry
- address: zookeeper://127.0.0.1:2181
- config-center:
- address: zookeeper://127.0.0.1:2181
- metadata-report:
- address: zookeeper://127.0.0.1:2181
启动zk server, 然后启动provider。

由图可知,在应用启动的时候成功进入到ZookeeperRegistryFactory。也就是说在配置ZK有效IP地址的情况下,我们在dubbo.intenal里配置SPI的实现类ZookeeperRegistryFactory得到了装载,从这里可以发现我们可以借助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范围内可使用。