# 第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中提倡使用插件扩展的方式增加自定义的功能实现。