# 第7节.Nacos中的SPI扩展加载代码设计

# 一、Java SPI的概念

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架 (opens new window)扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。可以实现让调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。

Java中的标准SPI实现:

1、自定义SPI的接口规范。

2、 然后需要在resources目录下新建META-INF/services目录,并且在这个目录下新建一个与上述接口的全限定名一致的文件,在这个文件中写入接口的实现类的全限定名。

3、使用ServiceLoader类进行加载

 ServiceLoader<CustomFactory> uploadCDN = ServiceLoader.load(CustomFactory.class);

Java中的SPI设计不足之处:

1、只能遍历所有的实现,并全部实例化。

2、配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。

3、扩展如果依赖其他的扩展,做不到自动注入和装配。

4、扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的Java SPI不支持

# 二、Nacos中的SPI扩展设计

nacos中对ServiceLoader类进行了基础的封装,将系统中的扩展统一加载到内存中,核心代码如下:

public class NacosServiceLoader {
    
    private static final Map<Class<?>, Collection<Class<?>>> SERVICES = new ConcurrentHashMap<Class<?>, Collection<Class<?>>>();
    
    public static <T> Collection<T> load(final Class<T> service) {
        if (SERVICES.containsKey(service)) {
            return newServiceInstances(service);
        }
        Collection<T> result = new LinkedHashSet<T>();
        for (T each : ServiceLoader.load(service)) {
            result.add(each);
            cacheServiceClass(service, each);
        }
        return result;
    }
    
    private static <T> void cacheServiceClass(final Class<T> service, final T instance) {
        if (!SERVICES.containsKey(service)) {
            SERVICES.put(service, new LinkedHashSet<Class<?>>());
        }
        SERVICES.get(service).add(instance.getClass());
    }
    
    public static <T> Collection<T> newServiceInstances(final Class<T> service) {
        return SERVICES.containsKey(service) ? newServiceInstancesFromCache(service) : Collections.<T>emptyList();
    }
    
    @SuppressWarnings("unchecked")
    private static <T> Collection<T> newServiceInstancesFromCache(Class<T> service) {
        Collection<T> result = new LinkedHashSet<T>();
        for (Class<?> each : SERVICES.get(service)) {
            result.add((T) newServiceInstance(each));
        }
        return result;
    }
    
    private static Object newServiceInstance(final Class<?> clazz) {
        try {
            return clazz.newInstance();
        } catch (IllegalAccessException | InstantiationException e) {
            throw new ServiceLoaderException(clazz, e);
        }
    }
}

SERVICES中存储了相关接口的SPI实现。通过这个设计,可以很好的达到扩展插件加载的功能。

# 2.1、加载客户端授权插件ClientAuthPlugin

ClientAuthPluginManager类使用该SPI工具类,加载客户端授权插件:

public void init(List<String> serverList, NacosRestTemplate nacosRestTemplate) {
    
    Collection<AbstractClientAuthService> clientAuthServices = NacosServiceLoader
            .load(AbstractClientAuthService.class);
    for (ClientAuthService clientAuthService : clientAuthServices) {
        clientAuthService.setServerList(serverList);
        clientAuthService.setNacosRestTemplate(nacosRestTemplate);
        clientAuthServiceHashSet.add(clientAuthService);
        LOGGER.info("[ClientAuthPluginManager] Load ClientAuthService {} success.",
                clientAuthService.getClass().getCanonicalName());
    }
    if (clientAuthServiceHashSet.isEmpty()) {
        LOGGER.warn("[ClientAuthPluginManager] Load ClientAuthService fail, No ClientAuthService implements");
    }
}

# 2.2、加载ID生成器插件

IdGeneratorManager类使用该SPI工具类,加载ID生成器实现:

private final Function<String, IdGenerator> supplier;

public IdGeneratorManager() {
    this.supplier = s -> {
        IdGenerator generator;
        Collection<IdGenerator> idGenerators = NacosServiceLoader.load(IdGenerator.class);
        Iterator<IdGenerator> iterator = idGenerators.iterator();
        if (iterator.hasNext()) {
            generator = iterator.next();
        } else {
            generator = new SnowFlowerIdGenerator();
        }
        generator.init();
        return generator;
    };
}

# 2.3、处理加解密插件

EncryptionPluginManager类使用该SPI工具类,处理加解密的实现,自己根据自己的加密算法进行设计和实现:

private static final Map<String, EncryptionPluginService> ENCRYPTION_SPI_MAP = new ConcurrentHashMap<>();

private static final EncryptionPluginManager INSTANCE = new EncryptionPluginManager();

public EncryptionPluginManager() {
    loadInitial();
}

/**
 * Load initial.
 */
private void loadInitial() {
    Collection<EncryptionPluginService> encryptionPluginServices = NacosServiceLoader.load(
            EncryptionPluginService.class);
    for (EncryptionPluginService encryptionPluginService : encryptionPluginServices) {
        if (StringUtils.isBlank(encryptionPluginService.algorithmName())) {
            LOGGER.warn("[EncryptionPluginManager] Load EncryptionPluginService({}) algorithmName(null/empty) fail."
                    + " Please Add algorithmName to resolve.", encryptionPluginService.getClass());
            continue;
        }
        ENCRYPTION_SPI_MAP.put(encryptionPluginService.algorithmName(), encryptionPluginService);
        LOGGER.info("[EncryptionPluginManager] Load EncryptionPluginService({}) algorithmName({}) successfully.",
                encryptionPluginService.getClass(), encryptionPluginService.algorithmName());
    }
}

类似使用的地方还有很多,比如:健康检查的处理、服务实例检查处理器、序列化工厂、事件中心的事件发布者等等。

在Nacos中提倡使用插件扩展的方式增加自定义的功能实现。

Last Updated: 10/5/2022, 4:38:08 PM