zoukankan      html  css  js  c++  java
  • dubbo学习(二)SPI2

    代码和示例的部分来自官方文档,本文只是经过润色

    概述

    SPI (Service Provider Interface)是JDK里面的扩展点发现机制。这个机制存在的动机是什么呢?服务发现。 往往我们的系统某个模块底层都有不同的实现,例如数据库的底层实现有 MySQL ,NoSQL 等,而 SPI 根据配置加载具体的实现,一个重要的作用就是解耦。

    dubbo 中的 SPI

    dubbo 中 SPI 改进的功能 :

    • 按需进行初始化,类似于spring 的 @Condition注解。
    • 解决JDK的 SPI 中的异常抛出,连扩展点的名称也拿不到
    • 增加 AOP 和 IOC

    SPI 使用示例

    java 中 SPI 使用示例

    public interface Robot {
        void sayHello();
    }
    

    接下来定义两个实现类,分别为 OptimusPrime 和 Bumblebee。

    public class OptimusPrime implements Robot {
        
        @Override
        public void sayHello() {
            System.out.println("Hello, I am Optimus Prime.");
        }
    }
    
    public class Bumblebee implements Robot {
    
        @Override
        public void sayHello() {
            System.out.println("Hello, I am Bumblebee.");
        }
    }
    
    

    接下来 META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot。文件内容为实现类的全限定的类名,如下:

    org.apache.spi.OptimusPrime
    org.apache.spi.Bumblebee
    

    做好所需的准备工作,接下来编写代码进行测试。

    public class JavaSPITest {
    
        @Test
        public void sayHello() throws Exception {
            ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
            System.out.println("Java SPI");
            serviceLoader.forEach(Robot::sayHello);
        }
    }
    

    我们这里可以看到SPI只要定义指定某个类具体类,最终某个具体类就会被加载进来,于是Dubbo 利用这个特性,在加载的过程中进行了扩展。后文会介绍如何扩展的

    Dubbo 中 SPI 使用示例

    Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。

    optimusPrime = org.apache.spi.OptimusPrime
    bumblebee = org.apache.spi.Bumblebee
    
    

    与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。下面来演示 Dubbo SPI 的用法:

    public class DubboSPITest {
    
        @Test
        public void sayHello() throws Exception {
            ExtensionLoader<Robot> extensionLoader = 
                ExtensionLoader.getExtensionLoader(Robot.class);
            Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
            optimusPrime.sayHello();
            Robot bumblebee = extensionLoader.getExtension("bumblebee");
            bumblebee.sayHello();
        }
    }
    
    

    Dubbo SPI 机制源码分析

    开始之前我们理清一下SPI 使用思路 :

    • SPI 注解
    • 指定具体实现类
    • 生成类

    涉及到 Dubbo SPI 一个重要的类就是 ExtensionLoader ,那么我们知道了这个类我们如何入手分析呢?在 dubbo 的测试案例中我们看到 ExtensionLoader 常见的用法如下 :

        final private Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
    
    

    可以看到通过这个静态方法就是获取得到 protocol 的具体对象。

        private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
    
        private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
        //这个type 在 ExtensionLoader 构造方法中赋值,它指的是加载什么类型的 Extension,在 ServiceConfig有调用 :
        //     private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
        private final Class<?> type;
    
        //保存多个 Extension 的对象
        private final ExtensionFactory objectFactory;
    
        private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
    
        private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
    
        // cache Activate class which is annotated with <code>Activate</code>
        private final Map<String, Object> cachedActivates = new ConcurrentHashMap<>();
        // 实体
        private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
        // cache Activate class which is annotated with <code>Activate</code>
        private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
    
        private volatile Class<?> cachedAdaptiveClass = null;
        private String cachedDefaultName;
    
    
        //可以看到上面的字段都是为了缓存 ExtensionLoader 和对应的实例 
    
        //返回一个 ExtensionLoader 对象 
        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 an interface!");
            }
            if (!withExtensionAnnotation(type)) {
                throw new IllegalArgumentException("Extension type (" + type +
                        ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
            }
    
            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;
        }
    
        public T getAdaptiveExtension() {
            Object instance = cachedAdaptiveInstance.get();
            if (instance == null) {
                if (createAdaptiveInstanceError != null) {
                    throw new IllegalStateException("Failed to create adaptive instance: " +
                            createAdaptiveInstanceError.toString(),
                            createAdaptiveInstanceError);
                }
    
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            //缓存不存在,创建一个 AdaptiveExtension,并放进缓存 
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            }
    
            return (T) instance;
        }
    
    
        private T createAdaptiveExtension() {
            try {
                //分两步 1.injectExtension 方法调用   2.getAdaptiveExtensionClass方法调用 
                return injectExtension((T) getAdaptiveExtensionClass().newInstance());
            } catch (Exception e) {
                throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
            }
        }
    
        private Class<?> getAdaptiveExtensionClass() {
            //先是根据策略加载了extension
            getExtensionClasses();
            if (cachedAdaptiveClass != null) {
                return cachedAdaptiveClass;
            }
            //// 创建自适应拓展类
            //假如没有存在 adaptiveClass 类的,什么情况没有?
            //比如该方法可以获取 Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等实现类。
            // 在获取实现类的过程中,如果某个实现类被 Adaptive 注解修饰了,那么该类就会被赋值给 cachedAdaptiveClass 变量。
            // 此时,上面步骤中的第二步条件成立(缓存不为空),直接返回 cachedAdaptiveClass 即可。
            // 如果所有的实现类均未被 Adaptive 注解修饰,那么执行第三步逻辑,创建自适应拓展类。
            return cachedAdaptiveClass = createAdaptiveExtensionClass();
        }
    
    
        //getExtensionClasses 这个方法用于获取某个接口的所有实现类。比如该方法可以获取 Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等实现类。在获取实现类的过程中,如果某个实现类被 Adaptive 注解修饰了,那么该类就会被赋值给 cachedAdaptiveClass 变量。
        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;
        }        
        
    
        /**
         * synchronized in getExtensionClasses
         * 该方法根据策略加载了 extension 类,并将 adaptive 类 active类都放进了缓存里
         * */
        private Map<String, Class<?>> loadExtensionClasses() {
            /*
             * 先是缓存带有 SPI 注释的类
             * 例如 :
             * @SPI("dubbo")
             * public interface Protocol {
             *     ...
             * }
             *
             */
            cacheDefaultExtensionName();
    
            Map<String, Class<?>> extensionClasses = new HashMap<>();
    
            for (LoadingStrategy strategy : strategies) {
                loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
                loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
            }
    
            return extensionClasses;
        }
    
    
        private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                                   boolean extensionLoaderClassLoaderFirst, String... excludedPackages) {
            String fileName = dir + type;
            try {
                
                ....
    
                if (urls != null) {
                    while (urls.hasMoreElements()) {
                        java.net.URL resourceURL = urls.nextElement();
                        //核心逻辑 
                        loadResource(extensionClasses, classLoader, resourceURL, excludedPackages);
                    }
                }
            } catch (Throwable t) {
                logger.error("Exception occurred when loading extension class (interface: " +
                        type + ", description file: " + fileName + ").", t);
            }
        }
    
    
        ....
    
    
      //最终调用到了这里  
      private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
            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.");
            }
            if (clazz.isAnnotationPresent(Adaptive.class)) {
                cacheAdaptiveClass(clazz);
            } else if (isWrapperClass(clazz)) {
                cacheWrapperClass(clazz);
            } else {
                //既没有注释 Adaptive 也不是包装类,仅仅是创建一个对象,然后进行保存而已 
                clazz.getConstructor();
                if (StringUtils.isEmpty(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, n);
                    }
                }
            }
        }
    
    
    
    

    到了这里方法还没有结束哦, injectExtension 的方法如下 (从方法名就可以知道是依赖注入):

        private T injectExtension(T instance) {
    
            if (objectFactory == null) {
                return instance;
            }
    
            try {
                for (Method method : instance.getClass().getMethods()) {
                    //检查是否是 set 方法
                    if (!isSetter(method)) {
                        continue;
                    }
                    /**
                     * Check {@link DisableInject} to see if we need auto injection for this property
                     */
                    if (method.getAnnotation(DisableInject.class) != null) {
                        continue;
                    }
                    Class<?> pt = method.getParameterTypes()[0];
                    if (ReflectUtils.isPrimitives(pt)) {
                        continue;
                    }
    
                    try {
                        //依赖注入
                        String property = getSetterProperty(method);
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            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;
        }
    
    
    

    ok,到此为止我们走完了Extension的 getAdaptiveExtension 的调用过程,而我们常常还会看看 getExtension 方法的调用。

    看一下 getExtension 方法

        public T getExtension(String name) {
            if (StringUtils.isEmpty(name)) {
                throw new IllegalArgumentException("Extension name == null");
            }
            if ("true".equals(name)) {
                //获取默认的拓展实现类
                return getDefaultExtension();
            }
            final Holder<Object> holder = getOrCreateHolder(name);
    
            //单例模式
            Object instance = holder.get();
            if (instance == null) {
                synchronized (holder) {
                    instance = holder.get();
                    if (instance == null) {
                        //
                        instance = createExtension(name);
                        holder.set(instance);
                    }
                }
            }
            return (T) instance;
        }
    
    
    
        /**
         *  第三步和第四步是为了日后 Dubbo 的 AOP 和 Ioc 是使用
         * @param name
         * @return
         */
        @SuppressWarnings("unchecked")
        private T createExtension(String name) {
            //No.1 从配置文件中加载, 例如 name 传进来的是 spi
            Class<?> clazz = getExtensionClasses().get(name);
            if (clazz == null) {
                throw findException(name);
            }
            try {
                T instance = (T) EXTENSION_INSTANCES.get(clazz);
                if (instance == null) {
                    //No.2 反射创建示例
                    EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                    instance = (T) EXTENSION_INSTANCES.get(clazz);
                }
                //No.3 向示例注入依赖
                injectExtension(instance);
                Set<Class<?>> wrapperClasses = cachedWrapperClasses;
                if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                    //No.4 循环创建 wrapper 实例
                    for (Class<?> wrapperClass : wrapperClasses) {
    
                        // 这个地方需要注意!!
                        // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
                        // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
                        // 所以返回的 instance 一定是一层嵌套一层的
    
                        
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                    }
                }
                initExtension(instance);
                return instance;
            } catch (Throwable t) {
                throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                        type + ") couldn't be instantiated: " + t.getMessage(), t);
            }
        }
    
    
    

    已 DubboProtocol 为例,返回来是这样的

    1297993-20200409141718014-806357246.png

    Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。

    总结

    本文从源码分析了 dubbo 的 SPI 机制和生成类的过程。

    参考资料

    https://zhuanlan.zhihu.com/p/44523895 学习的博客 https://www.cnkirito.moe/dubbo-nacos-stability/

    dubbo 的 example ,下载来看一下 https://github.com/apache/dubbo-samples/tree/master/java

  • 相关阅读:
    Django实现组合搜索的方法示例
    MySQL一些常用命令
    Linux IPMI 安装配置实用[转载]
    注意力训练的十个方法(转来的,原著者,不要打我)
    0001:Web与Web框架
    第二天Python
    第一天Python
    那些被疯狂追求的女孩,后来怎么样了?
    Linux服务器---流量监控MRTG
    Linux基础命令---文本编辑ex
  • 原文地址:https://www.cnblogs.com/Benjious/p/12666812.html
Copyright © 2011-2022 走看看