美文网首页dubbo
【Dubbo】SPI的来龙去脉

【Dubbo】SPI的来龙去脉

作者: 程序员驿站 | 来源:发表于2017-11-27 22:45 被阅读66次
    image.png

    JDK的SPI是为了解决什么问题?怎么实现?优缺点?

    定义

    全程 Service Provider Interface,Service提供者接口,引用别人的一句话 “提供给服务提供厂商与扩展框架功能的开发者使用的接口”
    说白了,就是插件式编程

    解决问题(引用官网)

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

    实现

    image.png

    定义服务接口

    package dictionary.spi;
    
    public interface Dictionary {
        String getDefinition(String word);
    }
    

    服务提供者的实现

    package dictionary.spi.impl;
    
    import dictionary.spi.Dictionary;
    
    import java.util.SortedMap;
    import java.util.TreeMap;
    
    public class ExtendedDictionary implements Dictionary {
        private SortedMap<String, String> map;
    
        /**
         * Creates a new instance of ExtendedDictionary
         */
        public ExtendedDictionary() {
            map = new TreeMap<String, String>();
            map.put(
                    "xml",
                    "a document standard often used in web services, among other " +
                            "things");
            map.put(
                    "REST",
                    "an architecture style for creating, reading, updating, " +
                            "and deleting data that attempts to use the common " +
                            "vocabulary of the HTTP protocol; Representational State " +
                            "Transfer");
        }
    
        @Override
        public String getDefinition(String word) {
            return map.get(word);
        }
    }
    
    

    注册服务

    在/src/main/resources/META-INF/services/建立dictionary.spi.Dictionary文件
    文件内容
    dictionary.spi.impl.ExtendedDictionary
    
    

    调用者

    
    package dictionary;
    
    import dictionary.spi.Dictionary;
    
    import java.util.Iterator;
    import java.util.ServiceLoader;
    
    public class DictionaryDemo {
    
        public static void main(String[] args) {
    
            ServiceLoader<Dictionary> loader = ServiceLoader.load(Dictionary.class);
    
            final Iterator<Dictionary> iterator = loader.iterator();
            while (iterator.hasNext()) {
                final Dictionary next = iterator.next();
                System.out.println(next.getDefinition("xml"));
                System.out.println(next.getDefinition("REST"));
            }
        }
    
    }
    
    

    实现机制

    通过上面的例子发现在调用者是基于ServiceLoader这个类实现的调用,我们翻翻这个ServiceLoader的源码
    在注释的第一行就写到“A simple service-provider loading facility.”
    我们看下的load的方法

    public static <S> ServiceLoader<S> load(Class<S> service) {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            return ServiceLoader.load(service, cl);
        }
    

    实际调用

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
            service = Objects.requireNonNull(svc, "Service interface cannot be null");
            loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
            acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
            reload();
        }
    

    在reload方法中最关键的是LazyIterator,这里面主要是nextService,里面有下段代码,是通过Class.forName实现

    try {
          c = Class.forName(cn, false, loader);
     } catch (ClassNotFoundException x) {
          fail(service,"Provider " + cn + " not found");
     }
    

    缺点

    • JDK的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
    • JDK spi不支持默认值
    • 要用for循环判断对象

    Dubbo为什么又实现了一套SPI?是解决了问题?

    针对JDK的问题,dubbo又实现SPI,主要解决下面问题

    1.支持缓存对象:spi的key与value 缓存在 cachedInstances对象里面,它是一个ConcurrentMap
    2.dubbo设计默认值:@SPI("dubbo") 代表默认的spi对象,例如Protocol的@SPI("dubbo")就是 DubboProtocol,
    通过 ExtensionLoader.getExtensionLoader(Protocol.class).getDefaultExtension()那默认对象
    3.根据KEY直接获取
    4.设计增加了AOP功能,在cachedWrapperClasses,在原始spi类,包装了XxxxFilterWrapper XxxxListenerWrapper
    5.dubbo设计增加了IOC,通过构造函数注入,代码为:wrapperClass.getConstructor(type).newInstance(instance),

    Dubbo根据JDK SPI机制的思想实现了自己的扩展点机制,ExtensionLoader是Dubbo扩展点机制的核心,相当于JDK SPI中的ServiceLoader

    如何使用

     public void test_useAdaptiveClass() throws Exception {
            ExtensionLoader<HasAdaptiveExt> loader = ExtensionLoader.getExtensionLoader(HasAdaptiveExt.class);
            HasAdaptiveExt ext = loader.getAdaptiveExtension();
            assertTrue(ext instanceof HasAdaptiveExt_ManualAdaptive);
        }
    
    /**
     * @author ding.lid
     */
    @SPI
    public interface HasAdaptiveExt {
        @Adaptive
        String echo(URL url, String s);
    }
    
    public class HasAdaptiveExtImpl1 implements HasAdaptiveExt {
        public String echo(URL url, String s) {
            return this.getClass().getSimpleName();
        }
    }
    
    @Adaptive
    public class HasAdaptiveExt_ManualAdaptive implements HasAdaptiveExt {
        public String echo(URL url, String s) {
            HasAdaptiveExt addExt1 = ExtensionLoader.getExtensionLoader(HasAdaptiveExt.class).getExtension(url.getParameter("key"));
            return addExt1.echo(url, s);
        }
    }
    

    在src/test/resources/META-INF/dubbo/internal目录下,有一个文件com.alibaba.dubbo.common.extensionloader.adaptive.HasAdaptiveExt
    内容如下

    adaptive=com.alibaba.dubbo.common.extensionloader.adaptive.impl.HasAdaptiveExt_ManualAdaptive
    impl1=com.alibaba.dubbo.common.extensionloader.adaptive.impl.HasAdaptiveExtImpl1
    配置格式:配置名=扩展实现类全限定名
    

    通过上面的例子,我们可以看出:

    先通过ExtensionLoader.getExtensionLoader加载一个接口
    通过getAdaptiveExtension找到一个自适应的类
    找到具体的实现类

    • 注解的含义
    1. @SPI,扩展点接口的标识
    扩展点声明配置文件,格式修改。 以Protocol示例,配置文件META-INF/dubbo/com.xxx.Protocol内容: 由 
    com.foo.XxxProtocol
       com.foo.YyyProtocol
     改成使用KV格式 
    xxx=com.foo.XxxProtocol
       yyy=com.foo.YyyProtocol
    

    注意:当扩展点的static字段或方法签名上引用了三方库, 如果三方库不存在,会导致类初始化失败, Extension标识Dubbo就拿不到了,异常信息就和配置对应不起来。 比如: Extension("mina")加载失败, 当用户配置使用mina时,就会报找不到扩展点, 而不是报加载扩展点失败,以及失败原因。(dubbo的注释)

    1. @Adaptive (方法上或类上)
      Dubbo使用的扩展点获取。
    • 自动注入关联扩展点。
    • 自动Wrap上扩展点的Wrap类。
    • 缺省获得的的扩展点是一个Adaptive Instance。

    我们根据ExtensionLoader的源码来分析下Dubbo是如何实现的SPI

    先看下ExtensionLoader#getExtensionLoader方法(静态)

    /**
         * <ul>
         * <li>首先判断扩展点类型是否为空</li>
         * <li>判断是否是接口</li>
         * <li>判断是否标注了SPI注解</li>
         * <li>该扩展点是否已经创建过加载器实例</li>
         * <li>如果没有被创建,创建过加载器实例,并且放到缓存中</li>
         * </ul>
         *
         * @param type 接口类型
         * @param <T>
         * @return 具体对象
         */
        @SuppressWarnings("unchecked")
        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 interface!");
            }
            if (!withExtensionAnnotation(type)) {
                throw new IllegalArgumentException("Extension type(" + type +
                        ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
            }
    
            ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
            if (loader == null) {
                EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
                loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
            }
            return loader;
        }
    
    private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
    

    这里使用了ConcurrentMap,缓存了ExtensionLoader,key就是接口类型

    在 EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
    这句调用了

    private ExtensionLoader(Class<?> type) {
            this.type = type;
            objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
        }
    

    ExtensionFactory 就实现了SPI:


    屏幕快照 2017-12-07 下午3.54.36.png

    自适应 getAdaptiveExtension()

    获取一个扩展类,如果@Adaptive注解在类上就是一个装饰类;如果注解在方法上就是一个动态代理类,例如Protocol$Adaptive对象。

     @SuppressWarnings("unchecked")
        public T getAdaptiveExtension() {
            //先从实例缓存中查找实例对象
            Object instance = cachedAdaptiveInstance.get();
            // 用来缓存自适应实现类的实例
            //缓存中不存在 这里采用了两次检查
            if (instance == null) {
                if (createAdaptiveInstanceError == null) {
                    synchronized (cachedAdaptiveInstance) {
                        instance = cachedAdaptiveInstance.get();
                        if (instance == null) {
                            try {
                                // if为空,创建新的实例
                                instance = createAdaptiveExtension();
                                cachedAdaptiveInstance.set(instance);
                            } catch (Throwable t) {
                                createAdaptiveInstanceError = t;
                                throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                            }
                        }
                    }
                } else {
                    throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
                }
            }
    
            return (T) instance;
        }
    
    @SuppressWarnings("unchecked")
        private T createAdaptiveExtension() {
            try {
                return injectExtension((T) getAdaptiveExtensionClass().newInstance());
            } catch (Exception e) {
                throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
            }
        }
    

    getAdaptiveExtensionClass()

    
        private Class<?> getAdaptiveExtensionClass() {
            getExtensionClasses();
            if (cachedAdaptiveClass != null) {
                return cachedAdaptiveClass;
            }
            return cachedAdaptiveClass = createAdaptiveExtensionClass();
        }
    
        private Class<?> createAdaptiveExtensionClass() {
            // /组装自适应扩展点类的代码
            String code = createAdaptiveExtensionClassCode();
            //获取到应用的类加载器
            ClassLoader classLoader = findClassLoader();
            //获取编译器 默认使用javassist
            com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
            // 将代码转换成Class
            return compiler.compile(code, classLoader);
        }
    

    编译器的结构图:


    屏幕快照 2017-12-07 下午4.31.35.png
    /**
         * 组装自适应扩展点类的代码
         *
         * @return
         */
        private String createAdaptiveExtensionClassCode() {
           // 省略代码
        }
    
    

    injectExtension

    /**
         * 注入
         * @param instance
         * @return
         */
        private T injectExtension(T instance) {
            try {
                if (objectFactory != null) {
                    // 遍历实例的所有方法
                    for (Method method : instance.getClass().getMethods()) {
                        // 必须是public 且 set 且 有参数
                        if (method.getName().startsWith("set")
                                && method.getParameterTypes().length == 1
                                && Modifier.isPublic(method.getModifiers())) {
                            Class<?> pt = method.getParameterTypes()[0];
                            try {
                                String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                                // 根据ExtensionFactor 创建对象
                                Object object = objectFactory.getExtension(pt, property);
                                if (object != null) {
                                    method.invoke(instance, object);
                                }
                            } catch (Exception e) {
                                logger.error("fail to inject via method " + method.getName()
                                        + " of interface " + type.getName() + ": " + e.getMessage(), e);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
            return instance;
        }
    
    Object object = objectFactory.getExtension(pt, property); 
    

    这里面我们用spring的为例子

      public <T> T getExtension(Class<T> type, String name) {
            for (ApplicationContext context : contexts) {
                if (context.containsBean(name)) {
                    Object bean = context.getBean(name);
                    if (type.isInstance(bean)) {
                        return (T) bean;
                    }
                }
            }
            return null;
        }
    

    总结

    实现步骤

    • 先通过ExtensionLoader加载类
    • 找到自适应
    • 获取具体实现

    相关文章

      网友评论

        本文标题:【Dubbo】SPI的来龙去脉

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