Java SPI
2022-10-13 本文已影响0人
sunpy
什么是SPI
SPI是jdk中的一种服务发现机制,在java中可以用来扩展API和第三方实现。相比于API,可以动态替换发现。
我的理解:SPI是一种动态服务发现方式。如果我们想要调用别人实现的方法,那么肯定是调用别人的实现类来操作。但是java里面是面向接口编程,实现多个类之间的解耦。所以SPI操作就是你可以通过调用接口来调用实现类。那么如何才能知道接口和实现类之间的绑定呢?就是通过配置文件,那么我们怎么加载配置文件,使用Classloader类加载器去加载,这样就可以了。

jdk中使用SPI方式
- 在jar包的META-INF/services目录下创建一个以"接口全限定名"为命名的文件,内容为实现类的全限定名。
- 接口实现类所在的jar包在classpath下。
- 主程序通过java.util.ServiceLoader动态状态实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM。
- SPI的实现类必须带一个无参构造方法。
自定义SPI实现同包调用
思路:
- 创建META-INF/services目录,放在classpath下面(放在resources下面)。
- 在META-INF/services目录下创建一个以"接口全限定名"为命名的文件,内容为实现类的全限定名。
- 主程序通过java.util.ServiceLoader动态状态实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM。
目录结构:

com.sunpy.permissionservice.spi.ISpiSunpyService文件:
com.sunpy.permissionservice.spi.SpiSunpyServiceOne
com.sunpy.permissionservice.spi.SpiSunpyServiceTwo
接口和实现类:
public interface ISpiSunpyService {
public void outMsg();
}
public class SpiSunpyServiceOne implements ISpiSunpyService{
@Override
public void outMsg() {
System.out.println("SpiSunpyServiceOne服务信息");
}
}
public class SpiSunpyServiceTwo implements ISpiSunpyService{
@Override
public void outMsg() {
System.out.println("SpiSunpyServiceTwo服务信息");
}
}
测试:
@Test
public void spiTest() {
ServiceLoader<ISpiSunpyService> serviceLoader = ServiceLoader.load(ISpiSunpyService.class);
serviceLoader.forEach(ISpiSunpyService::outMsg);
}

自定义SPI实现引入jar包调用

public class SunpyTest {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/services/com.sunpy.spi.ISpiService";
public static Map<String, List<String>> loadClassName() throws IOException {
ClassLoader classLoader = SunpyTest.class.getClassLoader();
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
Map<String, List<String>> map = new HashMap<>();
List<String> list = new ArrayList<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
URLConnection urlConnection = url.openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String content = "";
while ((content = br.readLine()) != null) {
list.add(content);
}
map.put(FACTORIES_RESOURCE_LOCATION, list);
}
return map;
}
public static void doMethod() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Map<String, List<String>> map = loadClassName();
List<String> list = map.get(FACTORIES_RESOURCE_LOCATION);
for (String clazzName : list) {
Class<?> clazz = Class.forName(clazzName);
Method method = clazz.getMethod("outMsg");
method.invoke(clazz.newInstance());
}
}
public static void main(String[] args) throws Exception {
doMethod();
}
}
SPI应用场景
-
springboot中的SPI
配置文件:

获取spring中的实例,委派给SpringFactoriesLoader.loadFactoryNames加载全限定类名。
// 获取spring的实例
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
// 获取类加载器
ClassLoader classLoader = getClassLoader();
/**
* 使用给定的类加载器从“META-INF/spring.factories”加载给定类型的工厂实现的全限定类名。
*/
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 创建实例集合
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
SpringFactoriesLoader.loadFactoryNames的实现
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
// public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
查看:

实例化类:
@SuppressWarnings("unchecked")
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
// 拿着加载的所有的全限定名,进行实例化
for (String name : names) {
try {
// 反射加载全限定名的Class
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
// 反射获取Constructor类
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
// 反射通过构造器实例化类
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}
参考
https://xie.infoq.cn/article/2021f8d92c93c94b96ebf4a3d
https://www.cnblogs.com/xrq730/p/11440174.html
https://blog.csdn.net/weixin_43476824/article/details/125739140