SPI使用

作者: 念䋛 | 来源:发表于2021-07-23 16:31 被阅读0次

    SPI 是 Java 提供的一种服务加载方式,全名为 Service Provider Interface。
    根据 Java 的 SPI 规范,我们可以定义一个服务接口,具体的实现由对应的实现者去提供,即服务提供者。
    然后在使用的时候再根据 SPI 的规范去获取对应的服务提供者的服务实现。
    通过 SPI 服务加载机制进行服务的注册和发现,可以有效的避免在代码中将具体的服务提供者写死。从而可以基于接口编程,实现模块间的解耦。
    SPI 机制的约定
    在 META-INF/services/ 目录中创建以接口全限定名命名的文件,该文件内容为API具体实现类的全限定名
    使用 ServiceLoader 类动态加载 META-INF 中的实现类
    如 SPI 的实现类为 Jar 则需要放在主程序 ClassPath 中
    API 具体实现类必须有一个不带参数的构造方法
    概念链接:https://www.jianshu.com/p/dc7e012baca6

    示例
    接口

    public interface SpiTest {
        public void say();
    }
    

    实现类

    public class SpiTestImpl implements SpiTest {
        @Override
        public void say() {
            System.out.println ("SpiTestImpl类");
        }
    }
    

    实现类

    public class SpiTestImpl1 implements SpiTest {
        @Override
        public void say() {
            System.out.println ("SpiTestImpl1类");
        }
    }
    

    测试类

    public class MainTest {
        public static void main(String[] args) {
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            ServiceLoader<SpiTest> load = ServiceLoader.load (SpiTest.class, classLoader);
            Iterator<SpiTest> providers = load.iterator ();
            while(providers.hasNext()) {
                SpiTest ser = providers.next();
                ser.say ();
            }
        }
    }
    
    image.png

    com.shardingsphere.shardingsphere.spiTest.SpiTest文件

    com.shardingsphere.shardingsphere.spiTest.SpiTestImpl
    com.shardingsphere.shardingsphere.spiTest.SpiTestImpl1
    

    源码解析
    ServiceLoader类的成员变量

    public final class ServiceLoader<S> implements Iterable<S>
       //配置文件的路径,这里就知道为什么文件放到META-INF/services/下
        private static final String PREFIX = "META-INF/services/";
        //加载的类或接口
        private final Class<S> service;
       //类加载器
        private final ClassLoader loader;
       //上下文,关于安全的用
        private final AccessControlContext acc;
      //已经加载的类
        private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
      //迭代器,hasNextService方法中加载类
        private LazyIterator lookupIterator;
    }
    

    ServiceLoader<SpiTest> load = ServiceLoader.load (SpiTest.class, classLoader);
    load方法

        public static <S> ServiceLoader<S> load(Class<S> service,
                                                ClassLoader loader)
        {
            return new ServiceLoader<>(service, loader);
        }
    

    new ServiceLoader<>(service, loader);

        private ServiceLoader(Class<S> svc, ClassLoader cl) {
            service = Objects.requireNonNull(svc, "Service interface cannot be null");
    //获取类加载器
            loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
            acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
            reload();
        }
    

    reload方法

        public void reload() {
            providers.clear();
            lookupIterator = new LazyIterator(service, loader);
        }
    

    在测试类中,是通过遍历获取加载好的类
    那我们就关注一下hashNext方法

            public boolean hasNext() {
    //如果没有上下文的,acc是关于类安全的,调用也是本地方法,本人不是很理解
    //这里不解释了,主要关心hasNextService方法
                if (acc == null) {
                    return hasNextService();
                } else {
    //
                    PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
                        public Boolean run() { return hasNextService(); }
                    };
                    return AccessController.doPrivileged(action, acc);
                }
            }
    
    

    hasNextService方法

     private boolean hasNextService() {
                if (nextName != null) {
                    return true;
                }
                if (configs == null) {
                    try {
    //首先找到配置文件,
                        String fullName = PREFIX + service.getName();
                        if (loader == null)
                            configs = ClassLoader.getSystemResources(fullName);
                        else
    //加载配置文件
                            configs = loader.getResources(fullName);
                    } catch (IOException x) {
                        fail(service, "Error locating configuration files", x);
                    }
                }
                while ((pending == null) || !pending.hasNext()) {
                    if (!configs.hasMoreElements()) {
                        return false;
                    }
    //获取到文件中的两个类的全类名
                    pending = parse(service, configs.nextElement());
                }
    //nextName就是配置文件其中的一个类的全类名
                nextName = pending.next();
                return true;
            }
    

    在测试类中 调用了providers.next();方法

            public S next() {
                if (acc == null) {
    //我们继续关注nextService方法
                    return nextService();
                } else {
                    PrivilegedAction<S> action = new PrivilegedAction<S>() {
                        public S run() { return nextService(); }
                    };
                    return AccessController.doPrivileged(action, acc);
                }
            }
    

    nextService主要的代码,nextName为成员变量,就是配置文件其中一个全类名,
    用Class.forName的方式创建类

     String cn = nextName;
     nextName = null;
     c = Class.forName(cn, false, loader);
    

    SPI主要的功能就是将原有的项目解耦
    讲解一下SPI在ShardingSphere中的使用,我们都知道ShardingSphere主要应用于分库分表,Apache的顶级项目,国产项目,是我们的骄傲,那SPI如何在ShardingSphere使用的,如何解耦的
    spring.shardingsphere.sharding.tables.course.key-generator.type=SNOWFLAKE
    这个配置文件,是主键生成策略,雪花算法
    [图片上传失败...(image-b9d78b-1627028688785)]
    createDataSource方法启动时要调用的方法
    ShardingDataSourceFactory#createDataSource
    --return new ShardingDataSource(dataSourceMap, new ShardingRule(shardingRuleConfig, dataSourceMap.keySet()), props);
    查看 new ShardingRule(shardingRuleConfig, dataSourceMap.keySet()),

       private ShardingKeyGenerator createDefaultKeyGenerator(final KeyGeneratorConfiguration keyGeneratorConfiguration) {
    //ShardingKeyGeneratorServiceLoader的静态代码块,就是通过SPI的方式,加载类到SERVICE_MAP中
    //大家可以点进去看一下,这里就不进去看了
            ShardingKeyGeneratorServiceLoader serviceLoader = new ShardingKeyGeneratorServiceLoader();
    // serviceLoader.newService就是SPI,我们跟进一下
            return containsKeyGeneratorConfiguration(keyGeneratorConfiguration)
                    ? serviceLoader.newService(keyGeneratorConfiguration.getType(), keyGeneratorConfiguration.getProperties()) : serviceLoader.newService();
        }
    

    newService方法,type就是配置文件中的SNOWFLAKE

        public final T newService(final String type, final Properties props) {
            Collection<T> typeBasedServices = loadTypeBasedServices(type);
            if (typeBasedServices.isEmpty()) {
                throw new RuntimeException(String.format("Invalid `%s` SPI type `%s`.", classType.getName(), type));
            }
            T result = typeBasedServices.iterator().next();
            result.setProperties(props);
            return result;
        }
    

    loadTypeBasedServices方法,type为SNOWFLAKE

        private Collection<T> loadTypeBasedServices(final String type) {
    //classType为org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator
    //我们关注一下newServiceInstances方法
            return Collections2.filter(NewInstanceServiceLoader.newServiceInstances(classType), input -> type.equalsIgnoreCase(input.getType()));
        }
    
        public static <T> Collection<T> newServiceInstances(final Class<T> service) {
            Collection<T> result = new LinkedList<>();
    //SERVICE_MAP没有service元素,返回空
            if (null == SERVICE_MAP.get(service)) {
                return result;
            }
    //遍历
            for (Class<?> each : SERVICE_MAP.get(service)) {
                result.add((T) each.newInstance());
            }
            return result;
        }
    

    此时的SERVICE_MAP
    [图片上传失败...(image-47f41f-1627029550408)]
    总结一下,配置文件中的SNOWFLAKE,导致最终使用的是id加载方式为
    org.apache.shardingsphere.core.strategy.keygen.SnowflakeShardingKeyGenerator

    相关文章

      网友评论

          本文标题:SPI使用

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