SPI:
1.Java SPI(Service Provider Interface)
Java SPI 使用了 策略模式,一个接口 多种实现 只需要声明接口 具体的实现并不在程序中 直接确定 而是由程序之外的配置掌控,用于具体实现的装配 步骤如下:
??? 定义一个接口 及 对应方法 ??? 对接口进行实现 ??? 在META/service/ 目录下,创建一个 以 接口全路径 命名的文件 ??? 文件内中 为 具体实现类的 全路径名,如果有多个,用分行符 分隔 ??? 在代码中 通过 java.util.ServiceLoader 来加载 具体的 实现类 ?
我们最熟悉的就是mysql-connector驱动? Java定义了java.sql.Driver接口,然后数据库厂家来实现这套规范,msyql 、Oracle等等都去实现这个接口,就是使用的Java SPI机制
?但是JDK的SPI机制在某些方面又有些不足,引用官网的一句话:
- JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
- 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。
?
2.Dubbo的SPI
约定:
在扩展类的 jar 包内,在资源路径META-INF/dubbo(或META-INF/services、META-INF/dubbo/internal)下放置扩展点配置文件,文件名称为接口全限定名且无后缀,文件内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。
如果要定义dubbo SPI接口,需要在接口上添加@SPI注解,value为默认实现。 ?
@SPI注解可以使用在类,接口和枚举上,Dubbo框架中都是使用在接口上。它的主要作用就是标记该接口是一个Dubbo SPI 接口,即是一个扩展点,可以有多个不同的内置实现或者自定义实现。运行时需要通过配置找到具体的实现类。(然而说了这么多,刚开始看还是不知道什么叫做扩展点,扩展点有什么作用。第一次我看到这我也是懵逼的,问题不大继续看)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {
/**
* default extension name
* 我们可以看到这个value的默认值是一个空字符串,我们可以通过传递value值来指定这个接口的默认实现是什么, 比如Transporter 他的默认就是netty
*/
String value() default "";
}
@Adaptive可以标记在类、接口、枚举和方法上,但是整个Dubbo中只有几个地方使用在类级别上,如AdaptiveExtensionFactory 和AdaptiveCompiler,其余都标注在方法上。如果标记在接口的方法上,即方法级别的注解,则可以通过参数动态的获得实现类,怎么动态的获取需要看getExtension方法。在后面我会说到这里
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
/**
* Decide which target extension to be injected. The name of the target extension is decided by the parameter passed
* in the URL, and the parameter names are given by this method.
* <p>
* If the specified parameters are not found from {@link URL}, then the default extension will be used for
* dependency injection (specified in its interface's {@link SPI}).
* <p>
* For examples, given <code>String[] {"key1", "key2"}</code>:
* <ol>
* <li>find parameter 'key1' in URL, use its value as the extension's name</li>
* <li>try 'key2' for extension's name if 'key1' is not found (or its value is empty) in URL</li>
* <li>use default extension if 'key2' doesn't appear either</li>
* <li>otherwise, throw {@link IllegalStateException}</li>
* </ol>
* If default extension's name is not give on interface's {@link SPI}, then a name is generated from interface's
* class name with the rule: divide classname from capital char into several parts, and separate the parts with
* dot '.', for example: for {@code com.alibaba.dubbo.xxx.YyyInvokerWrapper}, its default name is
* <code>String[] {"yyy.invoker.wrapper"}</code>. This name will be used to search for parameter from URL.
*
* @return parameter key names in URL
*/
String[] value() default {};
}
@Activate可以标注在类、接口、枚举类和方法上。主要使用在有多个扩展点的实现、需要根据不同条件被激活的场景中,如果Filter需要多个同时激活,因为每个Filter实现的是不同功能。@Activate可传入的参数很多
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
/**
* Activate the current extension when one of the groups matches. The group passed into
* {@link ExtensionLoader#getActivateExtension(URL, String, String)} will be used for matching.
*
* @return group names to match
* @see ExtensionLoader#getActivateExtension(URL, String, String)
*/
String[] group() default {};
/**
* Activate the current extension when the specified keys appear in the URL's parameters.
* <p>
* For example, given <code>@Activate("cache, validation")</code>, the current extension will be return only when
* there's either <code>cache</code> or <code>validation</code> key appeared in the URL's parameters.
* </p>
*
* @return URL parameter keys
* @see ExtensionLoader#getActivateExtension(URL, String)
* @see ExtensionLoader#getActivateExtension(URL, String, String)
*/
String[] value() default {};
/**
* Relative ordering info, optional
*
* @return extension list which should be put before the current one
*/
String[] before() default {};
/**
* Relative ordering info, optional
*
* @return extension list which should be put after the current one
*/
String[] after() default {};
/**
* Absolute ordering info, optional
*
* @return absolute ordering info
*/
int order() default 0;
}
SPI的源码中最重要的一个类就是ExtensionLoader,我们只有拿到了这个扩展类加载器,那么才可以进行后面的操作
在看Dubbo的SPI的源码之前我们先写一个demo,看看怎么使用这个扩展机制。先学会使用再来研究源代码
我准备一个环境,一个接口两个实现类
/**
* @author wsf
* @since 20220224
*/
// 定义了一个接口 并且标注了该类是一个扩展点类,默认的扩展点就是@SPI的value值 car2
@SPI("car2")
public interface Car {
void print();
}
/**
* @author wsf
* @since 20220224
*/
public class Car1 implements Car{
@Override
public void print() {
System.out.println("car1....");
}
}
/**
* @author wsf
* @since 20220224
*/
public class Car2 implements Car{
@Override
public void print() {
System.out.println("car2....");
}
}
2.加入Dubbo的SPI的配置
??????? 1.接口名字就是这个文件的名字
??????? 2.文件里面就是一个key-value的形式
?3.做一个测试类看下
/**
* @author wsf
* @since 20220224
*/
public class SPITest {
public static void main(String[] args) {
ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
Car defaultExtension = extensionLoader.getDefaultExtension();
defaultExtension.print(); // 输出 car2....
//Car adaptiveExtension = extensionLoader.getAdaptiveExtension();
//adaptiveExtension.print();
}
}
?因为我们在SPI的注解上标注了car2是默认扩展类,所以这里输出了car2.... 这样就是一个简单的demo
|