zoukankan      html  css  js  c++  java
  • Dubbo SPI源码解析①

    ​ SPI英文全称为Service Provider Interface。它的作用就是将接口实现类的全限定名写在指定目录的配置文件中,使框架读取配置文件,从而加载实现类。这样我们就可以动态的为接口替换实现类,使得框架拓展性更高。Java其实也有原生的SPI机制,但是Dubbo并未使用它。学习Dubbo源码的前提就是得弄懂Dubbo SPI机制。

    0.Java SPI示例

    public interface Hello{
        void sayHello();
    }
    
    public class testA implements Hello{
        @Override
        public void sayHello(){
            System.out.println("Hello,I am A");
        }
    }
    
    public class testB implements Hello{
        @Override
        public void sayHello(){
            System.out.println("Hello,I am B");
        }
    }
    

    ​ 写好实现类和接口后,我们需要在META-INF/services目录下新建一个文件,名称为接口Hello的全限定名。然后在文件里面写上所有实现类的全限定名,例如:

    com.yelow.spi.testA 
    com.yelow.spi.testB
    

    ​ 测试

    public class JavaSPITest{
        @Test
        public void sayHello()  throws Exception{
            ServiceLoader<Hello> serviceLoader = ServiceLoader.load(Hello.class);
            serviceLoader.forEach(Hello::sayHello);
            //分别输出:
            //Hello,I am A
            //Hello,I am B
        }
    }
    

    1.Dubbo SPI示例

    ​ Dubbo SPI的使用上,和Java SPI类似的。先定义接口和实现类,接口前加上@SPI注解,代表一个拓展点。再创建一个配置文件。但是这个文件的路径应该在META-INF/dubbo/路径下。配置文件内容应该变成键值对形式,例如:

    testA = com.yelow.spi.testA 
    testB = com.yelow.spi.testB
    

    ​ 最后测试方法为:

    public class JavaSPITest{
        @Test
        public void sayHello()  throws Exception{
            ExtensionLoader<Hello> loader=ExtensionLoader.getExtensionLoader(Hello.class);
            //按需加载,参数为配置文件中的key值
            Hello testA=loader.getExtension("testA");
            testA.sayHello();
            //输出Hello,I am A
        }
    }
    

    2.Dubbo SPI源码分析

    ​ 上面简单演示了Dubbo的使用方法。先通过ExtensionLoader.getExtensionLoader获取ExtensionLoader对象。在通过这个对象的getExtension方法获取实现类对象。先看一下getExtensionLoader方法:

    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对象
        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;
    }
    

    ​ getExtensionLoader方法比较简单,我们接着看一下getExtension:

    public T getExtension(String name) {
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        if ("true".equals(name)) {
            //获取默认的实现类
            return getDefaultExtension();
        }
        //这个类用于持有目标对象,先从缓存中获取
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<Object>());
            holder = cachedInstances.get(name);
        }
        //获取实例对象
        Object instance = holder.get();
        //如果没有,就新建实例对象。这里是个双重检查。意义可参考单例模式
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    //创建实现类实例对象
                    instance = createExtension(name);
                    //赋值到holder中
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
    

    ​ 同样的,也是先检查缓存,没有缓存再新建。下面我们看一下是怎么新建实例对象的,进入到createExtension方法:

    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, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            //向实例对象中注入依赖
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    //将当前实例对象作为参数传给Wrapper的构造方法,并通过反射创建Wrapper对象
                    //再向wrapper实例对象中注入依赖,最后把wrapper赋值给instance
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
        }
    }
    

    ​ 上面的方法中,给instance赋值的那行代码稍微有点复杂,其实最终目的只是把实现类对象包裹在Wrapper对象中。从上面的注释看,createExtension方法的目的有四个。重点关注getExtensionClasses和injectExtension方法:

    private Map<String, Class<?>> getExtensionClasses() {
        //从缓存获取已经加载的实现类
        Map<String, Class<?>> classes = cachedClasses.get();
        //双重检查
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    //加载实现类
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }
    

    ​ 依旧是先检查缓存,没有缓存再新建。我们进入到loadExtensionClasses方法:

    private Map<String, Class<?>> loadExtensionClasses() {
        //获取SPI注解
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if (value != null && (value = value.trim()).length() > 0) {
                //对SPI注解的值进行拆分
                String[] names = NAME_SEPARATOR.split(value);
                if (names.length > 1) {
                    throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                            + ": " + Arrays.toString(names));
                }
                //设置默认名称
                if (names.length == 1) cachedDefaultName = names[0];
            }
        }
    
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        //加载指定文件夹下的配置文件
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }
    

    ​ 我们看一下loadFile方法指定的目录常量分别是啥:

    ​ 可以看到,前面示例说到的目录就是在这规定的。而META-INF/services/是为了兼容Java SPI,internal/是Dubbo内部自己的拓展类配置文件。最后我们分析一下loadFile方法:

    private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
        //文件夹路径+接口全限定名=配置文件具体路径
        String fileName = dir + type.getName();
        try {
            Enumeration<java.net.URL> urls;
            ClassLoader classLoader = findClassLoader();
            //根据文件名加载所有同名文件
            if (classLoader != null) {
                urls = classLoader.getResources(fileName);
            } else {
                urls = ClassLoader.getSystemResources(fileName);
            }
            //获取到文件后进行遍历读取配置文件
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL url = urls.nextElement();
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
                        try {
                            String line = null;
                            //按行读取
                            while ((line = reader.readLine()) != null) {
                                //解析配置文件
                                //通过反射加载实现类
                                //操作缓存
                                //略。。。,最好自己debug调试一下,最清楚
                            } // end of while read lines
                        } finally {
                            reader.close();
                        }
                    } catch (Throwable t) {
                        logger.error("Exception when load extension class(interface: " +
                                type + ", class file: " + url + ") in " + url, t);
                    }
                } // end of while urls
            }
        } catch (Throwable t) {
            logger.error("Exception when load extension class(interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }
    

    ​ 获取实现类的源码分析的差不多了,现在回到createExtension方法,接着看看injectExtension,也就是Dubbo的依赖注入功能。Dubbo IOC是通过setter方法注入依赖。它会通过反射获取实例的方法列表,再遍历方法是否具备setter方法的特征,若有就通过反射调用这个setter方法将依赖设置到目标对象中。代码分析如下:

    private T injectExtension(T instance) {
        try {
            if (objectFactory != null) {
                //遍历方法
                for (Method method : instance.getClass().getMethods()) {
                    //判断方法是否以set开头,且只有一个参数,且方法是public
                    if (method.getName().startsWith("set")
                            && method.getParameterTypes().length == 1
                            && Modifier.isPublic(method.getModifiers())) {
                                //获取setter方法参数类型
                        Class<?> pt = method.getParameterTypes()[0];
                        try {
                            //获取熟悉名,比如setName,其对应的属性应该是name
                            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                //通过反射调用setter方法完成依赖注入
                                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;
    }
    
  • 相关阅读:
    QOMO Linux 4.0 正式版发布
    LinkChecker 8.1 发布,网页链接检查
    pgBadger 2.1 发布,PG 日志分析
    Aletheia 0.1.1 发布,HTTP 调试工具
    Teiid 8.2 Beta1 发布,数据虚拟化系统
    zLogFabric 2.2 发布,集中式日志存储系统
    开源电子工作套件 Arduino Start Kit 登场
    Piwik 1.9 发布,网站访问统计系统
    Ruby 1.9.3p286 发布,安全修复版本
    toBraille 1.1.2 发布,Java 盲文库
  • 原文地址:https://www.cnblogs.com/lbhym/p/14192704.html
Copyright © 2011-2022 走看看