SPI机制(Service provider interface)、JAVA SPI、SpringBoot SPI、Dubbo SPI

SPI

首先介绍一下SPI的概念:Service provider interface ,为某个接口寻找服务实现的机制。或者说可以通过配置来获取实现子类,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

盲猜实现:需要在程序外规定一个放配置实现子类的地方,用以扫描发现服务(寻找服务);找到子类定义(全路径名)后,通过反射的方法实例化子类

JAVA SPI

JAVA 自己实现的SPI机制的放配置地方在ClassPath路径下的META-INF/services文件夹下,其定义是扫描文件夹下的文件,文件名对应接口的全限定名,文件中的每一行对应一个实现子类的全限定名。

ServiceLoader 是Java提供的获取实现子类实例的方法:通过ServiceLoader.load(Interface.class)或者Service.providers(Interface.class)方法拿到实现类的实例。

深入理解SPI机制:https://www.jianshu.com/p/3a3edbcd8f24

SpringBoot SPI

在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载。

从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回。而且不仅仅是会去ClassPath路径下查找,会扫描所有路径下的Jar包下的META-INF/spring.factories配置文件。

Dubbo SPI

官方:http://dubbo.apache.org/zh-cn/docs/dev/SPI.html

dubbo 的SPI 的扫描目录为META-INF/dubbo/接口全限定名,META-INF/dubbo/internal/接口全限定名,META-INF/services/接口全限定名,文件中配置的格式为配置名=扩展类全限定名,加载过程的实现为ExtensionLoader

META-INF/dubbo/internal/接口全限定名,这个一般是dubbo自己在dubbo.jar中自定义的,是dubbo已实现的扩展子类。

META-INF/services/接口全限定名,java spi会扫描的地方,dubbo也一起扫描了,当然文件存储格式依然是类似properties类型的( = )。

至于文件中配置的格式为配置名=扩展类全限定名的原因是:dubbo可以通过配置name的方式来确定需要加载的扩展类如,

dubbo中的SPI都需要在接口上注解**@SPI**,在ExtensionLoader.getExtensionLoader(type)的的时候会检查type是不是接口,且有没有注解SPI.class。

ExtensionLoader获取Extension的顺序

首先通过 ExtensionLoader 的 getExtensionLoader 方法从缓存中获取一个拓展类对应的 ExtensionLoader实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。

public void test() throws Exception {
	ExtensionLoader extensionLoader = 
		ExtensionLoader.getExtensionLoader(Robot.class);
	Protocol dubbo= extensionLoader.getExtension(dubbo);
	Invoker invoker...
	dubbo.export(invoker);
	Protocol rmi = extensionLoader.getExtension(rmi);
	rmi .export(invoker);
}

ExtensionLoader 的getExtensionClasses 就是根据注解SPI.class的接口名从3个配置地址查找配置文件,获取所有扩展类全限定名。

Dubbo SPI 除了支持按需加载接口实现类,还增加了 IOC 和 AOP 等特性,Dubbo IOC 的实现比较简单,仅支持 setter 方式注入。

如果扩展类实例没有被缓存,就创建一个新的,在此过程中需要:(1)通过 getExtensionClasses 获取拓展类;(2)通过反射创建实例;(3)通过扩展类的setter方法注入依赖***(IOC)***;(4)使用Wrapper类进行包装 ***(AOP)***。

private T createExtension(String name) {
    // 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表
    Class clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            // 通过反射创建实例
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // 向实例中注入依赖
        injectExtension(instance);
        Set> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
            // 循环创建 Wrapper 实例
            for (Class wrapperClass : wrapperClasses) {
                // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
                // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
                instance = injectExtension(
                    (T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException(...);
    }
}

扩展点自动包装 wrapper

首先,明确一个步骤(可以看上面的 ExtensionLoader获取Extension的顺序),wrapper在扩展类被实例化后,且依赖注入后,进行wrapper包装。

那么wrapper哪里来的呢?我们从配置文件中获取所有扩展类的时候都是需要执行getExtensionClasses()方法从缓存中找,找不到执行loadExtensionClasses()加载。之后的关键方法调用步骤是loadDirectory(...)->loadResource(...)->loadClass(...)。loadDirectory通过classLoader从目录中寻找配置文件并把配置文件封装为URL,loadResource读取文件获取扩展类全限定名及name配置,loadClass是最为重要的一步,如果这个扩展类实现了扩展点接口,且有参数为扩展点接口的构造器,则认为这个扩展类是包装类,会被存入cachedWrapperClasses中在(获取Extension的顺序)第4步中对扩展类进行包装。

自适应拓展

有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。

这就是dubbo 自适应拓展的作用,根据参数在调用拓展方法的时候确定实例化哪一个拓展(实例化过的就缓存)。

  • 1、首先 Dubbo 会为拓展接口生成具有代理功能的代码(字符串拼接JAVA代码,通过dubbo的Compiler编译为class,一般默认为javassist);
  • 2、然后通过 javassist 或 jdk 编译这段代码,得到 Class 类;
  • 3、最后再通过反射创建代理类。
Adaptive 注解:与自适应拓展息息相关的一个注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    String[] value() default {};
}

Adaptive 可注解在类或方法上,注解在类上,表示拓展的加载逻辑由人工编码完成。

***注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理逻辑。***这就是自适应拓展功能。没有注解Adaptive的方法默认生成抛出一个异常。

getAdaptiveExtension 方法是获取自适应拓展的入口方法。(getExtension获取所有拓展)

createAdaptiveExtensionClass();就是在缓存找不到时的第一个步骤,拼接自适应扩展的Java代码。

比如Protocol的自适应扩展代码是什么呢?

你可能感兴趣的:(java)