美文网首页Dubbo 框架
dubbo原理:SPI机制(一)

dubbo原理:SPI机制(一)

作者: aix91 | 来源:发表于2019-07-04 09:07 被阅读0次

    JDK SPI 和Dubbo SPI的区别:

    • JDK SPI 会一次性实例化所有配置的实例:如果某些实例在程序中并不需要,那将会是极大的浪费。Dubbo SPI只会实例化需要的类。
    • Dubbo SPI 还支持IOC(注入其他的实例)
    • Dubbo SPI的灵活性更强,功能也更加强大。

    Dubbo SPI 将分成两篇来研究:

    1. dubbo原理:SPI机制(一): 主要研究dubbo SPI的原理。
    2. dubbo原理:SPI机制(二),主要研究dubbo IOC的原理

    研究dubbo SPI机制时,文章均从dubbo的测试类开始,这样让读者更容易理解dubbo SPI的实际用途。

    1. 起点:测试类

    SimpleExt
    @SPI("impl1")
    public interface SimpleExt {
        // @Adaptive example, do not specify a explicit key.
        @Adaptive
        String echo(URL url, String s);
        @Adaptive({"key1", "key2"})
        String yell(URL url, String s);
        // no @Adaptive
        String bang(URL url, int i);
    }
    

    接口SimpleExt有三个实现类;并且SimpleExt添加类SPI注解,定义类两个Adaptive方法。
    我们先来看下面这段测试代码:

    SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
    Map<String, String> map = new HashMap<String, String>();
    URL url = new URL("p1", "1.2.3.4", 1010, "path1", map);
    String echo = ext.echo(url, "haha");
    assertEquals("Ext1Impl1-echo", echo);
    

    2. 获取ExtensionLoader

    首先会获取测试类的ExtensionLoader:每个类都会有一个ExtensionLoader,自适应类就是在ExtensionLoader中产生的。
    a. getExtensionLoader

    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type){
    
        //首先会校验传入的type:是否为空、是否是interface、是否使用类SPI注解。
        ... 
        // 尝试从缓存中获取ExtensionLoader,每个type都有一个ExtensionLoader
        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;
    }
    

    b. ExtensionLoader 构造函数

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

    ExtensionLoader的构造函数,其实就是采用SPI的方式,生成类一个ExtensionFactory类。我们来看看ExtensionFactory是如何生成的。

    3. 生成ExtensionFactory自适应类

    a. getAdaptiveExtensionClass
    ExtensionFactory自适应类的创建和其他自适应类的方式相同。

    private Class<?> getAdaptiveExtensionClass() {
        //获取ExtensionFactory接口可能的实现类;这一步和JDK一样,Dubbo到规定的几个路径下(比如META-INF/dubbo/internal)去寻找ExtensionFactory实现类
        getExtensionClasses();
        // 如果有哪个实现类标注类@Adaptive,在这里就直接返回
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
        }
    

    b. 配置文件加载
    与JDK SPI不同的是,Dubbo SPI 文件内容是“key=value”的形式:

    adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory
    spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
    

    getExtensionClasses 方法会读取SPI配置文件,将实现类保存在extensionClasses中

    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        //省略读文件的具体操作
        while ((line = reader.readLine()) != null) {
            ...
            name = line.substring(0, i).trim();//key
            line = line.substring(i + 1).trim();//value
            loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
            ....
        }
    }
    

    在loadClass时也有需要注意的地方,不是所有的实现类都会被添加到extenssionClasses中去的。

    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) {
        // 如果实现类有Adative注解,则直接将该类缓存起来
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            //就是:cachedAdaptiveClass = clazz;
            cacheAdaptiveClass(clazz);
        }else {
            //  将class保存到extensionClasses中
            saveInExtensionClass(extensionClasses, clazz, name);
        }
    }
    

    最终extensionClasses的值如下图:

    extensionClasses

    再回到getAdaptiveExtensionClass方法中,由于在loadSource时,AdaptiveExtensionFactory上有Adaptive注解,那么cachedAdaptiveClass = AdaptiveExtensionFactory.class。Dubbo直接默认指定了自适应类,直接返回。
    于是最终通过getAdaptiveExtensionClass返回的ExtensionFactory返回的是AdaptiveExtensionFactory类。

    5. 生成SimpleExt自适应类

    和生成ExtensionFactory类似,SimpleExt的自适应类也需要调用getAdaptiveExtension方法,loadSource
    a. loadSource
    同样也是在"META-INF/dubbo/internal" 路径下加载SimpleExt的实现类

    impl2=org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl2  # Comment 2
    impl1=org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl1#Hello World
    impl3=org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl3 # with head space
    

    在SPI配置文件中定义了三个实现类,我们来看看最终的extensionClasses

    SimpleExt
    由于在三个实现类,都没有使用Adaptive注解,因此会进入到createAdaptiveExtensionClass方法
    b. createAdaptiveExtensionClass
    createAdaptiveExtensionClass 通过AdaptiveClassCodeGenerator代码生成器生成代码,然后使用Dubbo定义的compiler将代码文件转换成Class,最终生成自适应类。
    代码生成的代码过于复杂,这里只展示生成代码最终的样子,以便理解。
    package org.apache.dubbo.common.extension.ext1;
    import org.apache.dubbo.common.extension.ExtensionLoader;
    
    public class SimpleExt$Adaptive implements org.apache.dubbo.common.extension.ext1.SimpleExt {
        public java.lang.String bang(org.apache.dubbo.common.URL arg0, int arg1)  {
            throw new UnsupportedOperationException("The method public abstract java.lang.String org.apache.dubbo.common.extension.ext1.SimpleExt.bang(org.apache.dubbo.common.URL,int) of interface org.apache.dubbo.common.extension.ext1.SimpleExt is not adaptive method!");
    }
    
    public java.lang.String echo(org.apache.dubbo.common.URL arg0, java.lang.String arg1)  {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("simple.ext", "impl1");
        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([simple.ext])");
    
        org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
        return extension.echo(arg0, arg1);
    }
    
    public java.lang.String yell(org.apache.dubbo.common.URL arg0, java.lang.String arg1)  {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));
        
        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([key1, key2])");
        org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
       
        return extension.yell(arg0, arg1);
    }
    

    从上面的代码中,我们可以发现以下几点:

    1. 没有使用@Adaptive的bang方法,直接抛出异常
    2. 自适应interface的方法中,一定要有URL参数:直接( 方法参数中直接有URL参数)、间接( 从参数中通过getUrl方法,获取URL);如果没有URL参数,就会抛出异常
    3. 对于没有指定parameter的方法,如echo方法,在取参数时,会将simple.ext作为key(将SimpleExt拆分开,并用"."隔开);指定类parameter的方法,就按照key的顺序来取。
    4. 自定义类的生成只有在调用方法的时候,才会在方法内生成
    5. 最后会根据key取得的参数,来决定生成哪一个实现类:
      ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName), 最后实现方法的调用。

    6. 回到测试

    让我们回到最初的测试:

    • 测试1:默认实现
    {
        SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
        Map<String, String> map = new HashMap<String, String>();
        URL url = new URL("p1", "1.2.3.4", 1010, "path1", map);
        String echo = ext.echo(url, "haha");
        assertEquals("Ext1Impl1-echo", echo);
    }
    

    由于SimpleExt在定义时指定默认的自适应类(@SPI("impl1")), 于是即使没有显式指明自适应的类,Dubbo也会为我们生成SimpleExtImpl1。

    • 测试2:使用默认key,指定实现
     {
        SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
        Map<String, String> map = new HashMap<String, String>();
        map.put("simple.ext", "impl2");
        URL url = new URL("p1", "1.2.3.4", 1010, "path1", map);
        String echo = ext.echo(url, "haha");
        assertEquals("Ext1Impl2-echo", echo);
    

    测试2中显式指明了要实现的自适应类impl2,于是最终生成的是SimpleExtImpl2.

    • 测试3: 使用自定义key,指定实现;key的优先级是从左到右的
    {
        SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
        Map<String, String> map = new HashMap<String, String>();
        map.put("key2", "impl2");
        URL url = new URL("p1", "1.2.3.4", 1010, "path1", map);
        String echo = ext.yell(url, "haha");
        assertEquals("Ext1Impl2-yell", echo);
        url = url.addParameter("key1", "impl3"); // note: URL is value's type
        echo = ext.yell(url, "haha");
        assertEquals("Ext1Impl3-yell", echo);
    }
    

    yell 方法自定义了key1,key2,于是就不能再使用"simple.ext"作为key了。

    • 测试4: 没有url参数
        SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
        try {
            ext.echo(null, "haha");
            fail();
        } catch (IllegalArgumentException e) {
            assertEquals("url == null", e.getMessage());
        }
    

    在没有url参数的时候,就会抛出异常。

    相关文章

      网友评论

        本文标题:dubbo原理:SPI机制(一)

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