Java SPI (Service Provider Interface) 是一种重要的组件化方式,它可以让程序在运行时动态地装载一些实现模块,从而增强程序的可扩展性和灵活性。接下来将详细介绍 Java SPI 。

1. 什么是Java SPI

  Java SPI 是一种标准服务发现机制,在 Java 中被广泛应用。它可以让程序在启动时通过配置文件或者注解来动态加载对应的实现类,从而实现轻量级的插件式开发。具体调用实现如下:

2、具体怎么实现SPI机制呢

1、定义一个用于调用方法的接口

package com.huq;

public interface SPIService {
    void doSomething();
}

2、实现方法的具体功能,这里举例不同输出验证。

SPIServiceImpl

package com.huq.impl;

import com.huq.SPIService;

public class SPIServiceImpl implements SPIService {
    @Override
    public void doSomething() {
        System.out.printf("SPIServiceImpl doSomething");
    }
}


SPIServiceImplOther

package com.huq.impl;

import com.huq.SPIService;

public class SPIServiceImplOther implements SPIService {
    @Override
    public void doSomething() {
        System.out.printf("SPIServiceImplOther  doSomething");
    }
}

3.配置接口文件

在目录resources目录下,创建目录 META-INF、services,之后以接口包路径创建文件名:com.huq.SPIService,具体如下:

** 4、打包** 打包成jar文件,供其他项目调用。

5、 在其他项目引用上面创建好的jar包并应用

调用代码如下:

package com.huq;

import java.util.ServiceLoader;

//TIP 要<b>运行</b>代码,请按 <shortcut actionId="Run"/> 或
// 点击装订区域中的 <icon src="AllIcons.Actions.Execute"/> 图标。
public class Main {
    public static void main(String[] args) {
            final ServiceLoader<SPIService> serviceLoader = ServiceLoader.load(SPIService.class);

            serviceLoader.forEach(service -> {
                service.doSomething();
                System.out.println(service.getClass().getName().trim()+" exec ");
            });
        }
}

执行结果如下: 说明调用到了jar包中定义的方法。

应用场景

  1. JDBC驱动,加载不同数据库的驱动类

  2. Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等

  3. Tomcat 加载 META-INF/services下找需要加载的类

  4. SpringBoot项目中 使用@SpringBootApplication注解时,会开始自动配置,而启动配置则会去扫描META-INF/spring.factories下的配置类

优缺点

优点

解耦 使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起,应用程序可以根据实际业务情况启用框架扩展或替换框架组件。相比使用提供接口jar包,供第三方服务模块实现接口的方式,SPI的方式使得源框架,不必关心接口的实现类的路径

缺点

  1. 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类

  2. 多个并发多线程使用ServiceLoader类的实例是不安全的