dubbo spi

1.简介

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组

件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。

SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

2.Java SPI 示例

通过一个示例演示 Java SPI 的使用方法。

public interface IRobot {

    String sayHello();
}

public class BigBee implements IRobot {

    @Override
    public String sayHello() {
        return "BigBee";
    }
}

public class OptimusPrime implements IRobot {

    @Override
    public String sayHello() {
        return "OptimusPrime";
    }
}

在resources下创建一个META-INF/services/com.katcat.myserviceloader.provider.service.IRobot
com.katcat.myserviceloader.bigbee.impl.OptimusPrime
com.katcat.myserviceloader.bigbee.impl.BigBee

public class MainApp {

    public static void main(String[] args) {
        ServiceLoader printerLoader = ServiceLoader.load(IRobot.class);
        for (IRobot robot : printerLoader) {
            System.out.println(robot.sayHello());
        }
    }
}

重点是ServiceLoader和LazyIterator。

ServiceLoader重写了iterator方法,实际是lookupIterator.next();

LazyIterator重要的四个方法,1.hasNextService 2.nextService 3.hasNext 4.next,前两个是private,为后面两个public服务的。

看源码。

问题:

  • JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK标准的ScriptEngine,通过getName();获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。

  • 多个并发多线程使用ServiceLoader类的实例是不安全的。

3.DUBBO SPI 源码分析

准备工作:

1.ExtensionLoader 扩展点加载器。

2.ExtensionFactory 扩展类实现类实例工厂。

3.@SPI 扩展点接口的标识。

4.@Adaptive 标注在类上或者方法上,表示需要被扩展。

5.@Activate 可以被框架中自动激活的加载扩展

3.1 Protocol

以Protocol为例,有现成的好几个扩展,且后续服务暴露和引用都和其有关。

@SPI("dubbo")->看Protocol代码
@Adaptive
 Exporter export(Invoker invoker) throws RpcException;
@Adaptive
 Invoker refer(Class type, URL url) throws RpcException;

adaptive设计的目的是为了识别固定已知类和扩展未知类。
1.注解在类上:代表人工实现,实现一个装饰类(设计模式中的装饰模式),它主要作用于固定已知类,
目前整个系统只有2个,AdaptiveCompiler、AdaptiveExtensionFactory。
a.为什么AdaptiveCompiler这个类是固定已知的?因为整个框架仅支持Javassist和JdkCompiler。
a.为什么AdaptiveExtensionFactory这个类是固定已知的?因为整个框架仅支持2个objFactory,一个是spi,另一个是spring
2.注解在方法上:代表自动生成和编译一个动态的Adpative类,它主要是用于SPI,因为spi的类是不固定、未知的扩展类,所以设计了动态$Adaptive类.

//以这行代码来讲解
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("thrift");

3.2 核心的类 ExtensionLoader

重点关注以下三个方法

  • getActivateExtension :根据条件获取当前扩展可自动激活的实现
  • getExtension : 根据名称获取当前扩展的指定实现
  • getAdaptiveExtension : 获取当前扩展的自适应实现

/**
 * 私有的构造方法
 * 提供getExtensionLoader(java.lang.Class)出去
 * 节省系统资源
 * @param type
 */
private ExtensionLoader(Class type) {
    this.type = type;
    //ExtensionFactory主要用于加载扩展的实现
    //通过getAdaptiveExtension方法获取一个运行时自适应的扩展类型(每个Extension只能有一个@Adaptive类型的实现,如果没有dubbo会动态生成一个类
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

public static  ExtensionLoader getExtensionLoader(Class type) {
    if (type == null) {
        throw new IllegalArgumentException("Extension type == null");
    }
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    }
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type +
                ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }
    //从缓存获取一个ExtensionLoader,
    // 第一步会先创建一个ExtensionFactory的ExtensionLoader,
    // 第二步会创建一个Compire的ExtensionLoader
    //最后成功创建Protocol的ExtensionLoader
    ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        //保证对于每一个扩展,dubbo中只会有一个对应的ExtensionLoader实例。
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
        loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

ExtensionFactory看这个类的源码

AdaptiveExtensionFactory -> 源码

getAdaptiveExtension调用流程

看源码

  1. public T getAdaptiveExtension()
  2. -> createAdaptiveExtension() #如果无缓存则创建
  3. -> getAdaptiveExtensionClass().newInstance() #获取AdaptiveExtensionClass
  4. -> getExtensionClasses() # 加载当前扩展所有实现,看是否有实现被标注为@Adaptive
  5. -> createAdaptiveExtensionClass() #如果没有实现被标注为@Adaptive,则动态创建一个Adaptive实现类
  6. -> createAdaptiveExtensionClassCode() #动态生成实现类java代码
  7. -> compiler.compile(code, classLoader) #动态编译java代码,加载类并实例化
  8. -> injectExtension(instance)

getExtension调用流程

看源码

  1. getExtension(name)
  2. -> createExtension(name) #如果无缓存则创建
  3. -> getExtensionClasses().get(name) #获取name对应的扩展类型
  4. -> 实例化扩展类
  5. -> injectExtension(instance) # 扩展点注入
  6. -> instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)) #循环遍历所有wrapper实现,实例化wrapper并进行扩展点注入
//DI IOC
private T injectExtension(T instance) {
    try {
        //ExtensionFactory的objectFactory是null
        if (objectFactory != null) {
            //拿到所有的方法
            for (Method method : instance.getClass().getMethods()) {
                //判断是否是set开头的方法
                if (isSetter(method)) {
                    /**
                     * Check {@link DisableInject} to see if we need auto injection for this property
                     */
                    if (method.getAnnotation(DisableInject.class) != null) {
                        continue;
                    }
                    Class pt = method.getParameterTypes()[0];
                    if (ReflectUtils.isPrimitives(pt)) {
                        continue;
                    }
                    try {
                        //拿到需要set的属性名
                        String property = getSetterProperty(method);
                        //获取该属性名对应的扩展
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            method.invoke(instance, object);
                        }
                    } catch (Exception e) {
                        logger.error("Failed to inject via method " + method.getName()
                                + " of interface " + type.getName() + ": " + e.getMessage(), e);
                    }
                }
            }
        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

  • 每个ExtensionLoader实例只负责加载一个特定扩展点实现
  • 每个扩展点对应最多只有一个ExtensionLoader实例
  • 对于每个扩展点实现,最多只会有一个实例
  • 一个扩展点实现可以对应多个名称(逗号分隔)
  • 对于需要等到运行时才能决定使用哪一个具体实现的扩展点,应获取其自使用扩展点实现(AdaptiveExtension)
  • @Adaptive注解要么注释在扩展点@SPI的方法上,要么注释在其实现类的类定义上
  • 如果@Adaptive注解注释在@SPI接口的方法上,那么原则上该接口所有方法都应该加@Adaptive注解(自动生成的实现中默认为注解的方法抛异常)
  • 每个扩展点最多只能有一个被AdaptiveExtension
  • 每个扩展点可以有多个可自动激活的扩展点实现(使用@Activate注解)

4.总结

java spi和dubbo spi的区别

1.Dubbo 支持更多的加载路径

2.java需要加载所有的实现类,dubbo不是通过Iterator的形式,而是直接通过名称来定位具体的Provider,按需要加载,效率更高。

3.增加了对扩展点IoC支持,一个扩展点可以直接setter注入其它扩展点。

4.线程安全

附录:

o spi

FROM : https://www.cnblogs.com/heart-king/p/5632524.html

你可能感兴趣的:(dubbo spi)