我是靠谱客的博主 轻松长颈鹿,最近开发中收集的这篇文章主要介绍5.Dubbo源码分析----SPI机制,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

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

1.SPI机制介绍

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

2.JAVA的SPI机制

8438756-4242351a30e14365.png
目录结构
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.目录结构
8438756-9efa3fe41f53a8fa.png
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机制的全部内容,希望文章能够帮你解决5.Dubbo源码分析----SPI机制所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(51)

评论列表共有 0 条评论

立即
投稿
返回
顶部