SPI使用

2021-07-23  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读