美文网首页
自己动手实现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