一.SPI的源码解析(1)

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机制在某些方面又有些不足,引用官网的一句话:

  1. JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  2. 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
  3. 增加了对扩展点 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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值