美文网首页
自己动手实现2 - SPI 可扩展框架

自己动手实现2 - SPI 可扩展框架

作者: 原水寒 | 来源:发表于2019-01-13 16:09 被阅读51次

    SPI 可扩展框架:是各种 rpc 框架用于实现高可扩展性的手段。

    本节实现最基本的 SPI 机制,包含四个基本部分:

    • @Extension:该注解添加在可扩展接口的 实现上,并且通常会添加一个 alias 用于标识一个扩展实现类的 key(在 core-spi 中仅仅适用于标识)
    • ExtensionClass:一个 实现类 会最终被其 ExtensionLoader 加载为一个 ExtensionClass,存储在其
      ExtensionLoader 中,并且包含了实例化 ExtensionClass 存储的 实现类 的方法
    • ExtensionLoader:每一个 可扩展接口 都有且仅有一个 ExtensionLoader,用于从相应接口的 SPI 配置文件中读取配置内容并且将每一行解析成一个 ExtensionClass(每一个 ExtensionClass 对应一个实现,SPI 配置文件中的每一行配置一个实现类),之后存储 <alias, ExtensionClass> 配置对到 Map<String, ExtensionClass<T>> 容器中
    • ExtensionLoaderFactory:用来获取或者创建 ExtensionLoader,将创建好的 ExtensionLoader 放置在 Map<Class, ExtensionLoader> 容器中

    代码地址:https://github.com/zhaojigang/core-spi

    一、Extension

    /**
     * 扩展接口实现类的标识
     */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    public @interface Extension {
        /**
         * 在 core-spi 中不起作用,仅用作 alias 标识
         *
         * @return alias
         */
        String value();
    }
    

    二、ExtensionClass

    /**
     * 扩展实现类类 Class 包装类
     *
     * @param <T>
     */
    public class ExtensionClass<T> {
        /**
         * 真实的扩展实现类类 Class
         */
        private Class<? extends T> clazz;
    
        public ExtensionClass(Class<? extends T> clazz) {
            this.clazz = clazz;
        }
    
        /**
         * 调用无参构造器创建扩展实现类实例
         *
         * @return 扩展实现类实例
         */
        public T getExtInstance() {
            if (clazz == null) {
                throw new RuntimeException("Class of ExtensionClass is null");
            }
            try {
                return clazz.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException("create " + clazz.getCanonicalName() + " instance error", e);
            }
        }
    
        /**
         * 调用有参构造器创建扩展实现类实例
         *
         * @return 扩展实现类实例
         */
        public T getExtInstance(Class[] argTypes, Object[] args) {
            if (clazz == null) {
                throw new RuntimeException("Class of ExtensionClass is null");
            }
            try {
                Constructor<? extends T> constructor = clazz.getDeclaredConstructor(argTypes);
                return constructor.newInstance(args);
            } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
                throw new RuntimeException("create " + clazz.getCanonicalName() + " instance error", e);
            }
        }
    }
    

    三、ExtensionLoader

    /**
     * 扩展加载器
     *
     * @param <T>
     */
    public class ExtensionLoader<T> {
        private static final Logger LOGGER = LoggerFactory.getLogger(ExtensionLoader.class);
        /**
         * 当前扩展加载器处理的扩展接口名
         */
        private String interfaceName;
        /**
         * interfaceName 扩展接口下的所有实现
         */
        private Map<String, ExtensionClass<T>> alias2ExtensionClass;
    
        public ExtensionLoader(Class<T> interfaceClass) {
            this.interfaceName = interfaceClass.getName();
            this.alias2ExtensionClass = new ConcurrentHashMap<>();
            // 此处只指定了一个 spi 文件存储的路径
            loadFromFile("META-INF/services/corespi/");
        }
    
        private void loadFromFile(String spiConfigPath) {
            String spiFile = spiConfigPath + interfaceName;
            try {
                ClassLoader classLoader = this.getClass().getClassLoader();
                loadFromClassLoader(classLoader, spiFile);
            } catch (Exception e) {
                LOGGER.error("load file {} error, ", spiFile, e);
            }
        }
    
        private void loadFromClassLoader(ClassLoader classLoader, String spiFile) throws IOException {
            // 读取多个spi文件
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources(spiFile) : ClassLoader.getSystemResources(spiFile);
            if (urls == null) {
                return;
            }
            while (urls.hasMoreElements()) {
                // 每一个 url 是一个文件
                URL url = urls.nextElement();
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        // 读取文件中的每一行
                        readLine(line);
                    }
                } catch (Exception e) { // 文件需要整体失败,不能单行失败
                    LOGGER.error("load {} fail,", spiFile, e);
                }
            }
        }
    
        private void readLine(String line) throws ClassNotFoundException {
            // spi 文件需要严格按照 alias=className 格式编写
            String[] aliasAndClassName = line.split("=");
            // 任何不是 alias=className 格式的行都直接过滤掉
            if (aliasAndClassName == null || aliasAndClassName.length != 2) {
                return;
            }
            String alias = aliasAndClassName[0].trim();
            String className = aliasAndClassName[1].trim();
            Class<?> clazz = Class.forName(className, false, this.getClass().getClassLoader());
    
            // 必须具有扩展注解
            Extension extension = clazz.getAnnotation(Extension.class);
            if (extension == null) {
                LOGGER.error("{} need @Extension", className);
                return;
            }
    
            // 创建 ExtensionClass
            ExtensionClass<T> extensionClass = new ExtensionClass<>((Class<? extends T>) clazz);
            alias2ExtensionClass.putIfAbsent(alias, extensionClass);
        }
    
        public T getExtension(String alias) {
            ExtensionClass<T> extensionClass = alias2ExtensionClass.get(alias);
            if (extensionClass == null) {
                throw new RuntimeException("Not found extension of " + interfaceName + " named: \"" + alias + "\"!");
            }
            return extensionClass.getExtInstance();
        }
    
        public T getExtension(String alias, Class[] argTypes, Object[] args) {
            ExtensionClass<T> extensionClass = alias2ExtensionClass.get(alias);
            if (extensionClass == null) {
                throw new RuntimeException("Not found extension of " + interfaceName + " named: \"" + alias + "\"!");
            }
            return extensionClass.getExtInstance(argTypes, args);
        }
    }
    
    • core-spi 规定了 spi 文件存储的唯一路径,且指定了 alias=className 这样的唯一格式 - 由于这样的强规定,@Extension 注解中 value 属性(即 alias)不再有用,只是作为一个标识,可直接去掉
    • SOFARPC 除了实现了基本的 spi 机制之外,还实现了如下功能,具体见:SOFARPC 源码分析2 - SPI 扩展机制
    • spi 文件路径的可配置化
    • spi 配置文件的名字的可配置化(默认是可扩展接口全类名)
    • spi 配置的格式的多样性
    • 高 order 的实现类覆盖低 order 实现的功能
    • 排斥掉指定的低 order 的扩展点的功能
    • 实现类是否需要编码
    • 实现类是否需要单例

    四、ExtensionLoaderFactory

    /**
     * 创建 ExtensionLoader 的工厂
     */
    public class ExtensionLoaderFactory {
        /**
         * key: 扩展接口 Class
         * value: 扩展接口对应的 ExtensionLoader(单例,每一个扩展接口有一个 ExtensionLoader)
         */
        private static final Map<Class, ExtensionLoader> LOADER_MAP = new ConcurrentHashMap<>();
    
        /**
         * 获取或创建 clazz 扩展接口的 ExtensionLoader
         *
         * @param clazz 扩展接口
         * @param <T>
         * @return clazz 扩展接口的 ExtensionLoader
         */
        public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> clazz) {
            ExtensionLoader<T> loader = LOADER_MAP.get(clazz);
            if (loader == null) {
                synchronized (ExtensionLoaderFactory.class) {
                    if (loader == null) {
                        loader = new ExtensionLoader<>(clazz);
                        LOADER_MAP.put(clazz, loader);
                    }
                }
            }
            return loader;
        }
    }
    

    五、测试

    image.png

    1、可扩展接口

    package com.core;
    
    public interface LoadBalancer {
        String selectProvider();
    }
    

    2、扩展接口实现类

    package com.core;
    
    @Extension("random")
    public class RandomLoadBalancer implements LoadBalancer {
        @Override
        public String selectProvider() {
            return "random: 10.211.55.10:8080";
        }
    }
    
    @Extension("hasArgs")
    public class HasArgsLoadBalancer implements LoadBalancer {
        private String tag;
    
        public HasArgsLoadBalancer(String tag){
            this.tag = tag;
        }
    
        @Override
        public String selectProvider() {
            return "hasArgs: 10.211.55.11:8080 - " + tag;
        }
    }
    

    3、spi 配置文件

    文件位置:META-INF/services/corespi/com.core.LoadBalancer

    random = com.core.RandomLoadBalancer
    hasArgs = com.core.HasArgsLoadBalancer
    

    4、测试 SPI

    public class TestSPI {
        @Test
        public void testMainFunc() {
            // 1. 获取 LoadBalancer 的 ExtensionLoader
            ExtensionLoader<LoadBalancer> loader = ExtensionLoaderFactory.getExtensionLoader(LoadBalancer.class);
            // 2. 根据 alias 获取具体的 Extension
            LoadBalancer loadBalancer = loader.getExtension("random");
            // 3. 使用具体的 loadBalancer
            System.out.println(loadBalancer.selectProvider());
    
            // 4. 根据 alias 获取具体的 Extension
            LoadBalancer hasArgsLoadBalancer = loader.getExtension("hasArgs", new Class[]{String.class}, new Object[]{"haha"});
            // 5. 使用具体的 loadBalancer
            System.out.println(hasArgsLoadBalancer.selectProvider());
        }
    }
    

    相关文章

      网友评论

          本文标题:自己动手实现2 - SPI 可扩展框架

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