dubbo spi

作者: katkrazy | 来源:发表于2019-06-13 11:28 被阅读0次

    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<IRobot> 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

    <pre style="margin: 10px 0px 0px; padding: 0px; font-family: ConfluenceInstalledFont, monospace; color: rgb(23, 43, 77); font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">以Protocol为例,有现成的好几个扩展,且后续服务暴露和引用都和其有关。</pre>

    @SPI("dubbo")->看Protocol代码
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    @Adaptive
    <T> Invoker<T> refer(Class<T> 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 <T> ExtensionLoader<T> getExtensionLoader(Class<T> 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<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        if (loader == null) {
            //保证对于每一个扩展,dubbo中只会有一个对应的ExtensionLoader实例。
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
    
    

    ExtensionFactory看这个类的源码

    <pre style="margin: 10px 0px 0px; padding: 0px; font-family: ConfluenceInstalledFont, monospace; color: rgb(23, 43, 77); font-size: 14px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">AdaptiveExtensionFactory -> 源码</pre>

    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

        本文链接:https://www.haomeiwen.com/subject/ebmjfctx.html