Java SPI

什么是SPI?

SPI全称(Service Provider Interface)即 服务提供者接口,它是JDK内置的一种服务提供发现机制,这样说可能比较抽象,下面我们来举个例子来类比一下:

Spring项目中,我们写service层的时候都会约定俗成的有一个接口来定义规范,然后通过spring中的依赖注入,可以使用@Autowired等方式注入这个接口实现类的实例对象,之后的调用操作都是对基于这个接口调用。

简单来说就是这样的:

Java SPI与Spring SPI全面解析(源码级别讲解)_SPI


如图所示,接口、实现类都是由服务提供方提供,我们可以把controller看作是服务调用方,调用方只关心接口的调用就好了。

在上面这个例子里,service层和其中的方法我们都可以称之为API,而我们要讨论的SPI和它相比,有类似也有差异,看以下图:


Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_02


简单来说,就是定义一个接口规范,可以由不同的服务提供者实现,并且,调用方可以通过某种机制来发现服务提供方,并通过这个接口调用它的能力。

通过对比,我们可以看出它们虽然都有着接口这一层面,但还是有很大不同。

API中的接口只是给我们调用方提供一个功能列表,调用方直接调用就完事了,而SPI是定义一个接口规范,提供给服务方实现,然后调用方可以通过某种机制发现服务提供者。

说白了就是我给你接口规范,你只要按照我的规范去实现,我就能通过某种机制发现服务提供者,并且通过这个接口调用它的能力。

SPI有什么用?有什么好处?

简单来说,就是我们定义一个标准接口,让第三方平台去实现这个接口,调用者可以根据配置来动态加载不同的第三方库,实现动态扩展功能。例如: 实现我这个接口的厂商有A、B、C,那么我就可以根据配置来加载其中一个库。

好处就是解耦、可插拔、面向接口编程、动态类加载、扩展性强。

如果这么说还是有点抽象,那么就看下面的案例


SPI实现

定义接口

在这个案例里我们就以智能电风扇为例,一般智能电风扇的核心功能有: 开关、摆头、定时、调节档次。

假设我们有A、B、C三个不同型号的智能电风扇,但是我们的主要功能是一样的,设想一下,如果不同型号的智能电风扇各写各的接口,那么对接起来是不是很麻烦。

那么怎么解决呢?很简单,这个时候就用到了我们的SPI,我先定义一套接口规范,你按照我的接口规范实现,我就能通过某种服务发现机制找到你,使用这种方式的话,后续还有其他型号生产,只要按照这套规范实现,我都能找到。

废话不多说,上代码

新建一个项目,名为fan-spi-demo

Java SPI与Spring SPI全面解析(源码级别讲解)_String SPI_03

接下来新建一个接口,定义接口规范


package com.hmg.spi.service;

/**
 * @author hmg
 * @version 1.0
 * @date 2023-04-21 16:16
 * @description: fan core service
 */
public interface IFanCoreService {
    /**
     * 获取风扇类型
     * @return 类型
     */
    String getType();

    /**
     * 开关
     */
    void turnOnOff();

    /**
     * 调节风速
     */
    void adjustSpeed();

    /**
     * 摆头
     */
    void frontSway();

    /**
     * 定时
     */
    void timer();
}


这个接口定义好了,后面要给服务提供者实现,用maven把它打成jar包,方便给服务提供者引入

//使用这条命令打包
mvn clean install


打完包之后服务提供者就可以引入了

服务提供者实现

A模块

新建一个服务提供者A 模块,名为fanA-type-provider

Java SPI与Spring SPI全面解析(源码级别讲解)_String SPI_04


引入标准接口jar包

<dependencies>
        <dependency>
            <groupId>com.hmg.spi</groupId>
            <artifactId>fan-spi-demo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>


创建服务实现类


package com.hmg.spi.service.impl;

import com.hmg.spi.service.IFanCoreService;

/**
 * @author hmg
 * @version 1.0
 * @date 2023-04-21 16:44
 * @description: A型号风扇实现类
 */
public class AaTypeFanServiceImpl implements IFanCoreService {
    @Override
    public String getType() {
        return "A";
    }

    @Override
    public void turnOnOff() {
        System.out.println("A型号风扇开关");
    }

    @Override
    public void adjustSpeed() {
        System.out.println("A型号风扇调节风速");
    }

    @Override
    public void frontSway() {
        System.out.println("A型风扇摆头");
    }

    @Override
    public void timer() {
        System.out.println("A型风扇定时");
    }
}


在项目的resources目录下创建META-INF/services目录,然后以接口全限定名创建文件,并在文件中写入实现类的全限定类名

Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_05


这样我们就完成了一个服务提供者的实现,用maven打成jar包,就可以提供给调用方使用了

mvn clean install

B模块

接下来创建服务提供者B模块,和A模块同理


新建B模块

Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_06

在pom.xml中引入标准接口jar包


<dependencies>
        <dependency>
            <groupId>com.hmg.spi</groupId>
            <artifactId>fan-spi-demo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>


创建接口实现类


package com.hmg.spi.service.impl;

import com.hmg.spi.service.IFanCoreService;

/**
 * @author hmg
 * @version 1.0
 * @date 2023-04-21 17:36
 * @description:  B型号风扇实现类
 */
public class BbTypeFanServiceImpl implements IFanCoreService {
    @Override
    public String getType() {
        return "B";
    }

    @Override
    public void turnOnOff() {
        System.out.println("B型号风扇开关");
    }

    @Override
    public void adjustSpeed() {
        System.out.println("B型号风扇调节风速");
    }

    @Override
    public void frontSway() {
        System.out.println("B型风扇摆头");
    }

    @Override
    public void timer() {
        System.out.println("B型风扇定时");
    }
}


在resources目录下创建META-INF/services目录,新建一个普通文件,以接口全限定名作为文件名,文件内容就是实现类的全限定类名


Java SPI与Spring SPI全面解析(源码级别讲解)_String SPI_07


使用maven打成jar包,提供给调用方使用

mvn clean install


C模块

C模块同理,一样的操作,这里就不再演示了。


服务发现

现在三个服务提供者都实现了接口,下一步就是最关键的服务发现了,不过这一步java中的spi发现机制已经帮我们实现好了。


新建项目fan-app

Java SPI与Spring SPI全面解析(源码级别讲解)_String SPI_08


引入那三个服务提供者jar包


<dependencies>
    <dependency>
    <groupId>com.hmg.spi</groupId>
    <artifactId>fanA-type-provider</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
    <groupId>com.hmg.spi</groupId>
    <artifactId>fanB-type-provider</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
    <groupId>com.hmg.spi</groupId>
    <artifactId>fanC-type-provider</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
 </dependencies>


编写主程序代码


按照之前的说法,虽然每个服务提供者都对接口做了实现,但是调用者无需关心具体实现类,我们要做的是通过接口来调用服务提供者实现的方法。


下面就是关键的服务发现环节,我们编写一个方法,根据型号去调用智能电风扇的开关方法


package com.hmg.spi;

import com.hmg.spi.service.IFanCoreService;

import java.util.ServiceLoader;

/**
 * @author hmg
 * @version 1.0
 * @date 2023-04-21 23:37
 * @description: 调用者测试
 */
public class Main {
    /**
     * A型号的风扇
     */
    private final static String A_TYPE = "A";

    public static void main(String[] args) {
        new Main().turnOn(A_TYPE);
    }

    private void turnOn(String type){
        //通过ServiceLoader类的load方法去发现服务提供者并加载
        ServiceLoader<IFanCoreService> fanCoreServices = ServiceLoader.load(IFanCoreService.class);
        fanCoreServices.forEach(fanCoreService -> {
            System.out.println("检测到的类名:" + fanCoreService.getClass().getSimpleName());
            //判断是否是A型号的风扇,是的话直接开启
            if (type.equals(fanCoreService.getType())) {
                fanCoreService.turnOnOff();
            }
        });
    }
}


测试结果

Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_09



ServiceLoader原理

ServiceLoader.load()

ServiceLoader.load()方法

load()方法帮我们干了什么事呢?他其实就干了一件事,把接口.class和多线程上下文保存到LazyIterator(懒加载迭代器),我怎么知道的?当然是看源码,请看以下图:


Java SPI与Spring SPI全面解析(源码级别讲解)_String SPI_10


有读者可能会有疑问,线程的上下文类加载器是用来干嘛的?先别急,后面会说到,继续往下走,看源码:


新创建一个ServiceLoader对象,把服务和类加载器都传进去

Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_11

Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_12

Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_13

对于providers和lookupIterator属性,有些读者可能在这里会有疑问,这两个是什么?看图:


Java SPI与Spring SPI全面解析(源码级别讲解)_String SPI_14


从上面创建新的懒加载迭代器点进来看实现,对传进来的参数进行赋值


Java SPI与Spring SPI全面解析(源码级别讲解)_String SPI_15


到目前为止,load()方法就结束了,所以load方法干了什么事应该都有了大致了解。


接下来就是整个ServiceLoader的核心了, 当我们遍历ServiceLoader.load()方法得到的结果后,发现它会调用iterator()方法,为什么?当然是因为ServiceLoader实现了Iterable这个接口,细心的同学应该早就发现了,而整个服务发现的核心就在iterator()这个方法里,接下来让我们一探究竟。


iterator()

hasNext()


Java SPI与Spring SPI全面解析(源码级别讲解)_String SPI_16

lookupIterator.hasNext()

里面的acc是访问控制上下文,ServiceLoader创建的时候用的,前面判断的时候为空,所以这里直接看hasNextService()就好了


Java SPI与Spring SPI全面解析(源码级别讲解)_String SPI_17


hasNextService()

Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_18

路径前缀,所以这也就是为什么要在resources下创建META-INF/services目录了


Java SPI与Spring SPI全面解析(源码级别讲解)_String SPI_19


parse()

Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_20


parseLine()


Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_21

next()


Java SPI与Spring SPI全面解析(源码级别讲解)_String SPI_22

lookupIterator.next()

Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_23


nextService()

Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_24


至此,ServiceLoader的整个执行流程源码我们就看完了,在迭代器的迭代过程中,会完成所有实现类的实例化,其实归根结底,还是基于 java 反射去实现的。


小总结

大概的核心流程就是先通过ServiceLoader.load方法清空缓存,并且创建懒加载迭代器,把class对象和类加载器先装到懒加载迭代器,做好真正加载的工作,然后遍历load得到的结果,就来到了iterator()方法,进来就先获取缓存,然后来到hasNext()方法(这个方法主要用于判断缓存中是否有值,如果有直接返回true,没有的话就使用lookupIterator调用hasNext方法,hasNext方法里又调用hasNextService方法获取实现类全限定类名,放到nextName中,然后返回true),判断是否有下一个值完了之后,就到了next()方法,进来也是先判断缓存里是否有值,有的话直接返回,没有就使用lookupIterator调用next方法,next方法中调用nextService()方法(这个方法主要是通过Class.forName加载Class对象,然后通过Class对象的newIntstance()方法实例化实现类对象,最后再放到缓存里(全限定类名作为key,实例化后的实现类对象作为value,大概就是这样了,如果到这还是看不太懂的话,那就多debug看几遍就会了。


应用场景

SPI应用场景有很多,比如: Spring、Common-Logging、JDBC、Dubbo、可插拔架构、框架开发等等


总结

Java中的SPI机制整体上来说还是很不错的,通过接口灵活的将服务调用者与服务提供者分离,提供给第三方实现扩展时还是很方便的,不过它也有缺点,比如不能按需加载,只能一次性全部加载进来,如果加载到某些不需要的实现类,那就会造成资源浪费,还有每一个标准接口都需要创建一个新的文件来存放具体实现类,这是非常不方便的,最后ServiceLoader多线程使用是不安全的,不过整体上来说还是很不错的,提供了一种非常不错的思想。


Spring SPI

简介

看了上面的Java SPI,相信大家对SPI都有大概的认识了,其实Spring SPI也是基于JavaSPI思想来做的,从而实现了自己的SPI。


这里还是使用JavaSPI里的案例(智能电风扇)


定义接口模块

创建spring项目名为fan-spi-spring

Java SPI与Spring SPI全面解析(源码级别讲解)_String SPI_25


这里我还是使用聚合项目,我就不过多阐述这个了


引入spring依赖


<dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.26</version>
        </dependency>
    </dependencies>


定义接口


package com.hmg.spi.fanspi;

/**
 * @author hmg
 * @version 1.0
 * @date 2023-04-23 0:28
 * @description: fan core spi service
 */
public interface IFanCoreSpiService {
    /**
     * 获取风扇类型
     * @return 类型
     */
    String getType();

    /**
     * 开关
     */
    void turnOnOff();

    /**
     * 调节风速
     */
    void adjustSpeed();

    /**
     * 摆头
     */
    void frontSway();

    /**
     * 定时
     */
    void timer();
}


使用maven打包

mvn clean install


服务提供者实现

A模块

新建项目,名为fanA-type-provider-spring

Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_26



引入fan-spi-spring项目的依赖


<dependencies>
        <dependency>
            <groupId>com.hmg.spi</groupId>
            <artifactId>fan-spi-spring</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>


创建接口实现类


package com.hmg.spi.service.impl;

import com.hmg.spi.fanspi.IFanCoreSpiService;

/**
 * @author hmg
 * @version 1.0
 * @date 2023-04-23 0:50
 * @description: A型号风扇spi实现类
 */
public class AaTypeFanCoreSpiServiceImpl implements IFanCoreSpiService {
    @Override
    public String getType() {
        return "A";
    }

    @Override
    public void turnOnOff() {
        System.out.println("A型号风扇开关");
    }

    @Override
    public void adjustSpeed() {
        System.out.println("A型号风扇调节风速");
    }

    @Override
    public void frontSway() {
        System.out.println("A型风扇摆头");
    }

    @Override
    public void timer() {
        System.out.println("A型风扇定时");
    }
}


在resources目录下创建META-INF目录,并创建spring.factories文件,在文件里写入接口全限定名=实现类全限定类名,多个实现类用逗号隔开,例如:接口全限定名=实现类全限定类名, 实现类全限定类名


Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_27


使用maven打包,提供给调用者使用


mvn clean install


B模块

新建项目,名为fanB-type-provider-spring


Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_28


引入fan-spi-spring项目的依赖

<dependencies>
        <dependency>
            <groupId>com.hmg.spi</groupId>
            <artifactId>fan-spi-spring</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>


创建接口实现类


package com.hmg.spi.service.impl;

import com.hmg.spi.fanspi.IFanCoreSpiService;

/**
 * @author hmg
 * @version 1.0
 * @date 2023-04-23 1:02
 * @description: B型号风扇spi实现类
 */
public class BbTypeFanCoreSpiServiceImpl implements IFanCoreSpiService {
    @Override
    public String getType() {
        return "B";
    }

    @Override
    public void turnOnOff() {
        System.out.println("B型号风扇开关");
    }

    @Override
    public void adjustSpeed() {
        System.out.println("B型号风扇调节风速");
    }

    @Override
    public void frontSway() {
        System.out.println("B型风扇摆头");
    }

    @Override
    public void timer() {
        System.out.println("B型风扇定时");
    }
}


在resources目录下创建META-INF目录,并创建spring.factories文件,在文件里写入接口全限定名=实现类全限定类名,多个实现类用逗号隔开,例如:接口全限定名=实现类全限定类名, 实现类全限定类名

Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_29


使用maven打包,提供给调用者使用


mvn clean install


C模块

C模块同理,一样的操作,这里就不再演示了。


服务发现

新建项目fan-app-spring


Java SPI与Spring SPI全面解析(源码级别讲解)_String SPI_30


引入三个服务提供者模块的依赖


   

<dependencies>
        <dependency>
            <groupId>com.hmg.spi</groupId>
            <artifactId>fanA-type-provider-spring</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.hmg.spi</groupId>
            <artifactId>fanB-type-provider-spring</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.hmg.spi</groupId>
            <artifactId>fanC-type-provider-spring</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>



编写调用者代码


package com.hmg.spi;

import com.hmg.spi.fanspi.IFanCoreSpiService;
import org.springframework.core.io.support.SpringFactoriesLoader;

import java.util.List;

/**
 * @author hmg
 * @version 1.0
 * @date 2023-04-23 1:12
 * @description: 调用者测试
 */
public class Main {
    public static void main(String[] args) {
        new Main().turnOn("B");
    }

    private void turnOn(String type){
        //Spring SPI使用SpringFactoriesLoader去发现并加载实现类
        List<IFanCoreSpiService> fanCoreSpiServices =
                SpringFactoriesLoader.loadFactories(IFanCoreSpiService.class, Main.class.getClassLoader());

        fanCoreSpiServices.forEach(iFanCoreSpiService -> {
            System.out.println("检测到的实现类有:" + iFanCoreSpiService.getClass().getSimpleName());
            if (type.equals(iFanCoreSpiService.getType())) {
                iFanCoreSpiService.turnOnOff();
            }
        });
    }
}


测试


Java SPI与Spring SPI全面解析(源码级别讲解)_SPI_31


SpringFactoriesLoader原理

因为ServiceLoader基于图片形式讲解,感觉比较麻烦,所以SpringFactoriesLoader我还是贴代码讲解吧,在代码里加注释。


loadFactories(Class factoryType, @Nullable ClassLoader classLoader)

public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
        //断言,factoryType为空直接报错factoryType' must not be null
  Assert.notNull(factoryType, "'factoryType' must not be null");
  ClassLoader classLoaderToUse = classLoader;
        //判断是否有类加载器,没有的话就使用SpringFactoriesLoader的
  if (classLoaderToUse == null) {
  	classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
  }
        //加载实现类全限定名,我们进去看下loadFactoryNames()实现,看它怎么加载的实现类全限定名
  List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
  if (logger.isTraceEnabled()) {
  	logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
  }
  List<T> result = new ArrayList<>(factoryImplementationNames.size());
  for (String factoryImplementationName : factoryImplementationNames) {
            /**
            * 重点关注instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse),这里面主要是通过反射实例化对象
            * 接下来我们探究一下它的源码
            *
            **/
  	result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
  }
        
        //对结果进行排序
  AnnotationAwareOrderComparator.sort(result);
  return result;
	}


loadFactoryNames

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
  ClassLoader classLoaderToUse = classLoader;
        //判断是否有类加载器,没有的话就使用SpringFactoriesLoader的
  if (classLoaderToUse == null) {
  	classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
  }
        //获取接口全限定名
  String factoryTypeName = factoryType.getName();
        //主要看loadSpringFactories(classLoaderToUse)方法,这里面是找到spring.factories的源码
  return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}


loadSpringFactories

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        //先从缓存里面取,如果有数据直接返回,则继续往下执行(注意:key是classLoader)
  Map<String, List<String>> result = cache.get(classLoader);
  if (result != null) {
  	return result;
  }

  result = new HashMap<>();
  try {
            //根据路径获取所有资源,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加载,因为配置是K V的
    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) {
      //重新计算key,不存在则添加,存在则直接返回
      result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
        .add(factoryImplementationName.trim());
    	}
    }
  	}

  	//给结果去重
  	result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
    	.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
            
            //把结果添加到缓存里,classLoader作为key, 结果集作为value
  	cache.put(classLoader, result);
  }
  catch (IOException ex) {
  	throw new IllegalArgumentException("Unable to load factories from location [" +
    	FACTORIES_RESOURCE_LOCATION + "]", ex);
  }
        //返回结果
  return result;
	}



instantiateFactory

这个方法的实现就很少了,比较简单


private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
  try {
            //通过ClassUtils.forName()加载Class对象
  	Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
            //判断实现类是不是实现了标准接口
  	if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
    throw new IllegalArgumentException(
      "Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
  	}
            
            //先获得构造器,然后再实例化对象
  	return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
  }
  catch (Throwable ex) {
  	throw new IllegalArgumentException(
    "Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
    ex);
  }
	}


到这里SpringFactoriesLoader源码解读也就结束了,整体上还是比较简单的,阅读完源码后来个小总结


小总结

大概说一下核心流程,就是我们在进入loadFactories方法时,第一个核心点就是loadFactoryNames方法里调用的loadSpringFactories方法,它所做的事就是加载实现类全限定名,第二个核心点就是instantiateFactory方法了,它所做的事就是通过反射实例化对象了。


总结

阅读完SpringFactoriesLoader源码发现比JavaSPI的ServiceLoader简洁了不少,不过他们的整体流程相似度还是很高的,还有就是spring spi的所有配置是放到一个文件里的,省去了写一大堆文件的麻烦,而java spi是一个标准接口一个文件,这样的话就比较麻烦了。


JavaSPI 与 SpringSPI有什么区别?

它们最大的区别就是配置文件了,JavaSPI 是一个接口一个配置文件,而Spring SPI是集中在一个配置文件里,也就是spring.factories,还有一个就是java spi是在遍历的时候才真正加载实现类,而spring spi是在loadFactories的时候就加载了。