概述
概念
SPI(service provider interface)是JDK内置的中服务发现机制,和JNDI的作用一样,只是使用方式不一样。都可以用约定好的标识,去获相关服务的实现,区别是SPI通过接口类型去查询,JNDI通过服务的命名去查询,用法如下:
- 定义服务的接口
package com.test
public interface ServiceInterface {
String service();
}
- 实现接口
package com.test
public ServiceImp implements ServiceInterface {
String service() {
return "";
}
}
-
编写配置文件,文件路径必须为META-INF/service/com.test.ServiceInterface (接口名),配置文件中放实现类(com.test.ServiceImp)
-
编码获取service
public static void main(String[] args) {
ServiceLoader<ServiceInterface > services = ServiceLoader.load(ServiceInterface .class);
for (ServiceInterface service: services ){
System.out.println(service.service());
}
}
SPI的用途
SPI一般用于平台类软件,来实现插件的效果。当平台类软件运行时,有些功能必须由外部软件来实现,这样平台类可以定义好接口,外部软件按SPI规范实现好接口,并放入到类路径中,平台内软件运行时,就可以通过ServiceLoader获取到具体的实现,来实现可插拔,松耦合的效果。
比如,之前分析的spring通过实现ServletContainerInitializer接口,来实现servlet的配置。
spring SPI
spring也实现了自己的SPI功能,可能考虑到以下两点吧:
- SPI的配置文件的路径是约定好的,如果复用jdk的SPI机制,配置文件可能产生冲突,导致运行异常。
- spring想尽量小的依赖JDK
差异点
- 配置文件不一样,spring的配置文件路径为META-INF/spring.factories,且文件内容为键值对(接口=实现类)
- loader不一样JDK的是ServiceLoader,spring的是SpringFactoriesLoader(org.springframework.core.io.support)
SpringFactoriesLoader源码分析
service的加载过程其实比较简单,因为JDK中classLoader.getResources的方法可以方便的获取到类路径上的文件。
加载过程:
- 获取所有spring.factories文件的URL
- 通过URL获取输入流,并将文件内容存储到Properties类中
- 合并数据
// 代码有删减
public final class SpringFactoriesLoader {
// spring SPI配置文件的路径
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// 同一个接口可能有多个实现类,所以用MultiValueMap存储,一个key值可以对应多个value
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
// 获取所有的SPI
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// classLoader.getResources方法可以方便的获取到类路径上面的文件
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 使用Properties.load方法,去加载文件中的配置
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
// 配置文件中同一个接口的实现类,可以配置多个,中间使用逗号或分号分隔
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
return result;
}
// 其他方法为查询方法,从map中查询某个接口的实现类,没啥好说的
}
最后
以上就是外向星月为你收集整理的spring SPI SpringFactoriesLoader概念的全部内容,希望文章能够帮你解决spring SPI SpringFactoriesLoader概念所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复