SPI机制在JDK/Spring/SpringBoot的区别?
概要
SPI (Service Provider Interface) 是一种服务发现机制,它允许第三方提供者为核心库或主框架提供实现或扩展。这种设计允许核心库/框架在不修改自身代码的情况下,通过第三方实现来增强功能。
一、JDK原生的SPI
1. 定义和发现
JDK的SPI主要通过在META-INF/services/目录下放置特定的文件来指定哪些类实现了给定的服务接口。这些文件的名称应为接口的全限定名,内容为实现该接口的全限定类名。
2. 加载机制
ServiceLoader类使用Java的类加载器机制从META-INF/services/目录下加载和实例化服务提供者。例如,ServiceLoader.load(MyServiceInterface.class)会返回一个实现了MyServiceInterface的实例迭代器。
3. 应用示例
在Java的生态系统中,SPI 是一个核心概念,允许开发者提供扩展和替代的实现,而核心库或应用不必更改,下面举出一个例子来说明。
1)首先定义一个接口。
public interface HelloSpi {String getName();void handle(); }
2)定义不同的实现类。
全部代码和步骤如下:
步骤1:定义一个服务接口,文件名: MessageService.java
public class OneHelloSpiImpl implements HelloSpi {@Overridepublic String getName() {return "One";}@Overridepublic void handle() {System.out.println(getName() + "执行");} } public class TwoHelloSpiImpl implements HelloSpi {@Overridepublic String getName() {return "Two";}@Overridepublic void handle() {System.out.println(getName() + "执行");} }
3)在指定目录(META-INF.services)下创建文件
文件名是接口的全类名,文件内容是实现类的全类名。
这里创建的文件名是 org.example.chapter15.HelloSpi , 里面的内容为:
org.example.chapter15.OneHelloSpiImpl
org.example.chapter15.TwoHelloSpiImpl
4)测试
public class Test {public static void main(String[] args) {ServiceLoader<HelloSpi> load = ServiceLoader.load(HelloSpi.class);Iterator<HelloSpi> iterator = load.iterator();while (iterator.hasNext()) {HelloSpi next = iterator.next();System.out.println(next.getName() + " 准备执行");next.handle();}System.out.println("执行结束");} }// 执行结果 One 准备执行 One执行 Two 准备执行 Two执行 执行结束
通过执行结果我们可以看出,HelloSpi接口的所有实现类都得到了调用,我们可以通过这种机制根据不同的业务场景实现拓展的效果。示例是通过ServiceLoader实现的,我们来看一下这个类。
4. ServiceLoader
ServiceLoader是一个简单的服务提供者加载工具。是JDK6引进的一个特性。
部分源码如下:
public final class ServiceLoader<S> implements Iterable<S> {//指定服务提供者配置文件的基本路径private static final String PREFIX = "META-INF/services/"; private final ClassLoader loader;//构造器private ServiceLoader(Class<S> svc, ClassLoader cl) {service = Objects.requireNonNull(svc, "Service interface cannot be null");//默认是系统类加载器(也叫做应用程序类加载器)loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;reload();}public static <S> ServiceLoader<S> load(Class<S> service) {//获取当前线程的线程上下文类加载器ClassLoader cl = Thread.currentThread().getContextClassLoader();//调用下面的方法return ServiceLoader.load(service, cl);}//根据指定的服务接口(service)和类加载器(cl)加载服务提供者public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader) {return new ServiceLoader<>(service, loader);}//... }
load方法是通过获取当前线程的线程上下文类加载器实例来加载的。Java应用运行的初始线程的上下文类加载器默认是系统类加载器。这里其实破坏了双亲委派模型,因为Java应用收到类加载的请求时,按照双亲委派模型会向上请求父类加载器完成,这里并没有这么做 。
5. 缺点
JDK原生的SPI每次通过ServiceLoader加载时都会初始化一个新的实例,没有实现类的缓存,也没有考虑单例等高级功能。
6. 应用场景
Java中有许多我们常见的框架使用SPI机制的地方,JDBC,Dubbo,Logback等,Spring中也有使用。
二、Spring的SPI
Spring SPI对 Java SPI 进行了封装增强。我们只需要在 META-INF/spring.factories 中配置接口实现类名,即可通过服务发现机制,在运行时加载接口的实现类。
Spring的SPI不仅仅是服务发现,它提供了一套完整的插件机制。Spring的核心框架提供了很多接口和抽象类,如BeanPostProcessor, PropertySource, ApplicationContextInitializer等,这些都可以看作是Spring的SPI。开发者可以实现这些接口来扩展Spring的功能。这些接口允许开发者在Spring容器的生命周期的不同阶段介入,实现自己的逻辑。
1. 应用示例
1)配置
还是采用上面的接口定义与实现,配置放在 META-INF/spring.factories中
com.yp.service.HelloSpi=com.yp.service.impl.OneHelloSpiImpl,com.yp.service.impl.TwoHelloSpiImpl
2) 测试
public class HelloSpiTest {@Testpublic void testSpringSpi() {List<HelloSpi> helloSpiList = SpringFactoriesLoader.loadFactories(HelloSpi.class, this.getClass().getClassLoader());Iterator<HelloSpi> iterator = helloSpiList.iterator();while (iterator.hasNext()) {HelloSpi next = iterator.next();System.out.println(next.getName() + " 准备执行");next.handle();}System.out.println("执行结束");} }//执行结果为 sql 代码解读复制代码One 准备执行 One执行 Two 准备执行 Two执行 执行结束
1. 与IoC集成
与JDK的SPI不同,Spring的SPI与其IoC (Inversion of Control) 容器集成,使得在SPI实现中可以利用Spring的全部功能,如依赖注入。
2. 条件匹配:Spring提供了基于条件的匹配机制,这允许在某些条件下只加载特定的SPI实现,例如,可以基于当前运行环境的不同来选择加载哪个数据库驱动。
3. 配置
Spring允许通过spring.factories文件在META-INF目录下进行配置,这与JDK的SPI很相似,但它提供了更多的功能和灵活性。
三、SpringBoot的SPI
Spring Boot有一个与SPI相似的机制,但它并不完全等同于Java的标准SPI。
Spring Boot的自动配置机制主要依赖于spring.factories文件。这个文件可以在多个jar中存在,并且Spring Boot会加载所有可见的spring.factories文件。我们可以在这个文件中声明一系列的自动配置类,这样当满足某些条件时,这些配置类会自动被Spring Boot应用。
1. spring.factories机制
spring.factories是Spring Boot的一个特性,允许开发者自定义自动配置。通过spring.factories文件,开发者可以定义自己的自动配置类,这些类在Spring Boot启动时会被自动加载。
在这种情况下,SpringFactoriesLoader的使用,尤其是通过spring.factories文件来加载和实例化定义的类,可以看作是一种特定的SPI实现方式,但它特定于Spring Boot
参考链接:
https://juejin.cn/post/7197070078361387069