美文网首页
5.Dubbo源码分析----SPI机制

5.Dubbo源码分析----SPI机制

作者: szhlcy | 来源:发表于2019-03-26 15:05 被阅读0次

    在这里讲解GitHub上面最新的版本的Dubbo4.3.16版本。

    1.SPI机制介绍

     SPI机制,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。因此,很容易的通过 SPI 机制为我们的程序提供拓展功能。对于Java的原生SPI机制在这里不多做讲解,可以在网上搜索到很多讲解的。这里通过一个例子简单说明Java的SPI机制。

    2.JAVA的SPI机制

    目录结构
    1.创建一个基础的接口类
    public interface JavaSpiTestService {
        String test();
    }
    
    2.对上面创建地接口类进行实现
    public class JavaSpiTestServiceImpl implements JavaSpiTestService {
        @Override
        public String test() {
            return "测试";
        }
    }
    
    3.在resource目录下创建一个META-INF并在其下创建一个services目录,然后用上面创建的接口类的相对路径来创建一个文件名,文件内容是对应的实现类的相对路径
    othertest.demo.impl.JavaSpiTestServiceImpl
    
    4.进行测试
    public class MainTestClass {
        public static void main(String[] args) {
            ServiceLoader<JavaSpiTestService> load = ServiceLoader.load(JavaSpiTestService.class);
            for (JavaSpiTestService testService : load) {
                System.out.println(testService.test());;
            }
        }
    }
    

     运行结果为

    测试
    

    3.Dubbo的SPI机制

     Dubbo的SPI机制是重新实现的一套SPI机制。所有的逻辑都被封装在了ExtensionLoader类中。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。

    1.目录结构 dubbo自定义SPI扩展目录结构

     可以发现目录结构和Java的SPI机制结构相似。

    2.自定义接口类

    &mesp;在测试自定义的接口的时候,需要在类上加上@SPI这个注解

    @SPI
    public interface DubboSpiTestService {
        String sayHello();
    }
    
    3.对上面的自定义实现类进行实现
    public class DubboSpiTestServiceImplOne implements DubboSpiTestService {
        @Override
        public String sayHello() {
            return "我是Dubbo的SPI机制的第一个实现类";
        }
    }
    -----------
    public class DubboSpiTestServiceImplTwo implements DubboSpiTestService {
        @Override
        public String sayHello() {
            return "我是Dubbo的SPI机制的第二个实现";
        }
    }
    
    4.配置文件的内容

     Dubbo的SPI机制的配置未见的格式是键值对的形式,与Java的Spi配置文件不同

    dubboOne=othertest.demo.impl.DubboSpiTestServiceImplOne
    dubboTwo=othertest.demo.impl.DubboSpiTestServiceImplTwo
    
    5.测试
    public class MainTestClass {
        public static void main(String[] args) {
            ExtensionLoader<DubboSpiTestService> extensionLoader = ExtensionLoader.getExtensionLoader(DubboSpiTestService.class);
            DubboSpiTestService dubboOne = extensionLoader.getExtension("dubboOne");
            DubboSpiTestService dubboTwo = extensionLoader.getExtension("dubboTwo");
            System.out.println(dubboOne.sayHello());;
            System.out.println(dubboTwo.sayHello());;
        }
    }
    

    最后运行结果为

    我是Dubbo的SPI机制的第一个实现类
    我是Dubbo的SPI机制的第二个实现
    

    3.源码的解析

     我们进入到ExtensionLoader类,在上面的main方法中我们使用到的方法是getExtension,进入到这个方法。这个方法的作用是,根据传入的扩展名找到对应的实体类。

        public T getExtension(String name) {
            //检查扩展名是不是空,是空会抛出异常
            if (StringUtils.isEmpty(name)) {
                throw new IllegalArgumentException("Extension name == null");
            }
            //如果指定的扩展名是“true”,返回的则是null,dubbo会缓存一个cachedDefaultName的string类型字段,这个字段保存的是贴有@SPI标签的类,默认是类名,也可以设置
            if ("true".equals(name)) {
                return getDefaultExtension();
            }
            //在已经缓存的Hodler对象的示例缓存集合中查询是否存在对应的Holder
            Holder<Object> holder = getOrCreateHolder(name);
            Object instance = holder.get();
            //入伙对应的Holder不存在,则需要创建,并保存起来
            if (instance == null) {
                synchronized (holder) {
                    instance = holder.get();
                    if (instance == null) {
                        //创建实例拓展类
                        //------------2------
                        instance = createExtension(name);
                        //-------------2--------
                       //保存到缓存中 holder.set(instance);
                    }
                }
            }
            return (T) instance;
        }
    

     这里对上面的cachedDefaultName这个字段进行分析,找到这个字段的值进行设置的位置

        private String cachedDefaultName;
    
        private void cacheDefaultExtensionName() {
            //获取type类上的SPI标签,这里的Type是调用getExtensionLoader时候传入的,在上面的main方法中可以看到我们有传入一个类的Class对象,
            final SPI defaultAnnotation = type.getAnnotation(SPI.class);
            if (defaultAnnotation != null) {
                //获取到自定义的value
                String value = defaultAnnotation.value();
                //value的值只能存在一个,多余一个就会抛错
                if ((value = value.trim()).length() > 0) {
                    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];
                    }
                }
            }
        }
    

     对createExtension方法进行解析

        private T createExtension(String name) {
            //-------------1-------------
            //从配置文件中读取扩展类的路径并使用类加载器加载扩展类,默认使用的是ExtensionLoader加载器,
            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, clazz.newInstance());
                    instance = (T) EXTENSION_INSTANCES.get(clazz);
                }
                //----------------2------------
                //将实例中注入,使用setter方式进行注入
                injectExtension(instance);
                Set<Class<?>> wrapperClasses = cachedWrapperClasses;
                //如果缓存的wrapperClasses集合不是空则进行注入
                if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                    //循环创建wrapperClass
                    for (Class<?> wrapperClass : wrapperClasses) {
                        //获取wrapperClass的构造方法,并使用创建地实例作为构造参数创建wrapperClass对象,然后像Wrapper实例中注入依赖,然后赋值给instance实例
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                    }
                }
                return instance;
            } catch (Throwable t) {
                throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                        type + ") couldn't be instantiated: " + t.getMessage(), t);
            }
        }
    

     第一步,获取所有的扩展类;第二步,创建扩展类的实例;第三步,向扩展类中进行注入依赖;第四步,把拓展对象包裹在相应的 Wrapper 对象中

     解析getExtensionClasses方法

        private Map<String, Class<?>> getExtensionClasses() {
        //获取缓存的class对象,所有的class对象保存在一个Map中,Map对象又封装在Dubbo自定义的一个Holder对象中
            Map<String, Class<?>> classes = cachedClasses.get();
            //如果缓存不存在则加锁,则加锁,然后再次检车是不是null,加锁的原因是避免为null的时候多个线程同时执行获取扩展类的操作
            if (classes == null) {
                synchronized (cachedClasses) {
                    classes = cachedClasses.get();
                    if (classes == null) {
                    //获取扩展类,并缓存起来
                        classes = loadExtensionClasses();
                         cachedClasses.set(classes);
                    }
                }
            }
            return classes;
        }
        
    //--------------------loadExtensionClasses方法
    
        private Map<String, Class<?>> loadExtensionClasses() {
            //这个方法就是上面讲到的cachedDefaultName进行赋值的方法
            cacheDefaultExtensionName();
    
            Map<String, Class<?>> extensionClasses = new HashMap<>();
            //按照不同的目录来加再类
            loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
            loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
            loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
            loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
            loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
            loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
            return extensionClasses;
        }
        
    //-----------loadDirectory方法------
        private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
            //文件名=   文件夹路径 + type 全限定名 
            String fileName = dir + type;
            try {
                Enumeration<java.net.URL> urls;
                //获取ClassLoader,默认是先获取加载ExtensionLoader类线程的上下文加载器,如果不存在才才指定ExtensionLoader类的加载器,如果加载ExtensionLoader来的加载器都不存在,则使用bootstrap类加载器
                ClassLoader classLoader = findClassLoader();
                if (classLoader != null) {
                    urls = classLoader.getResources(fileName);
                } else {
                    urls = ClassLoader.getSystemResources(fileName);
                }
                if (urls != null) {
                    while (urls.hasMoreElements()) {
                        java.net.URL resourceURL = urls.nextElement();
                        //加载资源,主要就是加载配置文件然后加载文件中的类,并按照键值对的形式进行存储,其中需要注意的是有对贴有Activate标签的类的特殊处理,Activate标签的作用是在符合给定情况的时候去加载这个类 loadResource(extensionClasses, classLoader, resourceURL);
                    }
                }
            } catch (Throwable t) {
                logger.error("Exception occurred when loading extension class (interface: " +
                        type + ", description file: " + fileName + ").", t);
            }
        }  
    //------------loadResource方法中的loadClass,loadClass方法是加载类的主要逻辑
        private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        //检查传入的type类是不是需要实例化的class的父类或者接口类,如果不是则抛错
            if (!type.isAssignableFrom(clazz)) {
                throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                        type + ", class line: " + clazz.getName() + "), class "
                        + clazz.getName() + " is not subtype of interface.");
            }
            //如果类上有Adaptive标签,则保存到对应的缓存中
            if (clazz.isAnnotationPresent(Adaptive.class)) {
                cacheAdaptiveClass(clazz);
            } 
            //如果是wrapper类型,则保存到对应的缓存中
            else if (isWrapperClass(clazz)) {
                cacheWrapperClass(clazz);
            } else {
            // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
                clazz.getConstructor();
                if (StringUtils.isEmpty(name)) {
                // 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 name
                    name = findAnnotationName(clazz);
                    if (name.length() == 0) {
                        throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                    }
                }
    
                String[] names = NAME_SEPARATOR.split(name);
                if (ArrayUtils.isNotEmpty(names)) {
                    cacheActivateClass(clazz, names[0]);
                    for (String n : names) {
                        cacheName(clazz, n);
                        saveInExtensionClass(extensionClasses, clazz, name);
                    }
                }
            }
        }
    
    

     对于实例的依赖注入方法injectExtension解析

        private T injectExtension(T instance) {
            try {
                //这里的objectFactory类就是ExtensionFactory类的实现类AdaptiveExtensionFactory
                if (objectFactory != null) {
                    //便利实例类的方法
                    for (Method method : instance.getClass().getMethods()) {
                        //如果方法是已set开头的,且方法仅有一个参数,且方法访问级别为 public。用来确保是类的属性
                        if (isSetter(method)) {
                            /**
                             * Check {@link DisableInject} to see if we need auto injection for this property
                             */
                            //如果方法上面有DisableInject这个标签就不进行注入
                            if (method.getAnnotation(DisableInject.class) != null) {
                                continue;
                            }
                            //获取 setter 方法参数类型
                            Class<?> pt = method.getParameterTypes()[0];
                            //如果字段是基础类型或这是基础类型的数组,也不进行注入
                            if (ReflectUtils.isPrimitives(pt)) {
                                continue;
                            }
                            try {
                                //获取属性名,从setter方法中获取 setName 方法对应属性名 name
                                String property = getSetterProperty(method);
                                //获取依赖对象
                                Object object = objectFactory.getExtension(pt, property);
                                if (object != null) {
                                    //调用setter方法进行以来的注入
                                    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;
        }
    

     objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。

     其中SpringExtensionFactory在初始化的时候会将Spring的ApplicationContext保存在自己的内部contexts字段中,还会注册服务关闭的钩子方法和监听器,getExtension方法获取的是注册到Spring容器中的依赖对象

        public static void addApplicationContext(ApplicationContext context) {
            contexts.add(context);
            if (context instanceof ConfigurableApplicationContext) {
                ((ConfigurableApplicationContext) context).registerShutdownHook();
                DubboShutdownHook.getDubboShutdownHook().unregister();
            }
            BeanFactoryUtils.addApplicationListener(context, shutdownHookListener);
        }
        
        public <T> T getExtension(Class<T> type, String name) {
    
            //SPI should be get from SpiExtensionFactory
            //如果是通过自定义SPI扩展的就去SpiExtensionFactory中调用getExtension方法
            if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
                return null;
            }
            //获取容器中的对象
            for (ApplicationContext context : contexts) {
                if (context.containsBean(name)) {
                    Object bean = context.getBean(name);
                    if (type.isInstance(bean)) {
                        return (T) bean;
                    }
                }
            }
        .....
        }    
    


    在这里讲解GitHub上面最新的版本的Dubbo4.3.16版本,有部分变动。之前Dubbo版本有一个启动类DubboBootStrap类,这个类的作用是可以通过编程的方式轻松启动和停止Dubbo。其中在启动的时候会注册一个服务器关闭(这里的关闭不是强制关闭kill -9 pid 这种命令,而是kill pid这种温柔结束的方式)时候的钩子方法registerShutdownHook。这个方法会处理关闭的时候逻辑。现在这个方法在4.3.16版本中被放到了ConfigurableApplicationContext接口中,实现于AbstractApplicationContext类。在SpringExtensionFactory类中被调用。

    相关文章

      网友评论

          本文标题:5.Dubbo源码分析----SPI机制

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