目录
[2.SPI 的实现步骤](#2.SPI 的实现步骤)
[3.案例:验证码生成器的 SPI 实现](#3.案例:验证码生成器的 SPI 实现)
[3.1 定义服务接口(ICode)](#3.1 定义服务接口(ICode))
[3.2 编写服务实现类](#3.2 编写服务实现类)
[3.3 配置 SPI 文件](#3.3 配置 SPI 文件)
[3.4 使用ServiceLoader加载服务](#3.4 使用ServiceLoader加载服务)
[3.5 测试 SPI](#3.5 测试 SPI)
[3.6 运行结果:(在运行期间更改了SPI 配置文件的注释)](#3.6 运行结果:(在运行期间更改了SPI 配置文件的注释))
[3.7 核心代码解析](#3.7 核心代码解析)
SPI(Service Provider Interface) 是 Java 提供的一种 "服务发现" 机制,它允许我们在不修改代码的情况下,动态加载接口的实现类。本文将通过一个验证码生成的案例来体现 SPI 的原理与实践。
1.SPI的核心思想
SPI 是一种**服务发现机制,**让接口与实现完全解耦,核心思想是:(接口 + 实现 + 配置文件)
- 定义一个接口(服务接口)。
- 由第三方(服务提供者)实现该接口。
- 程序运行时,通过
ServiceLoader自动发现并加载所有实现类。
2.SPI 的实现步骤
-
定义服务接口:声明通用的服务规范。
-
编写服务实现:第三方实现服务接口。
-
配置 SPI 文件 :在
META-INF/services/下创建以接口为名的文件,内容为实现类名字。 -
使用
ServiceLoader加载服务:运行时自动发现并加载所有实现类。
3.案例:验证码生成器的 SPI 实现
项目结构:

3.1 定义服务接口(ICode)
java
package com.interfacess;
/**
* 验证码生成器接口(服务接口)
*/
public interface ICode {
public String makeCode();
}
3.2 编写服务实现类
创建两个实现类:ChineseCodeImpl(生成中文验证码)和NumberCodeImpl(生成数字验证码)。
中文验证码实现(ChineseCodeImpl)
java
package com.interfaces.impl;
import java.util.Random;
import com.interfacess.ICode;
/**
* 中文验证码实现类(服务提供者)
*/
public class ChineseCodeImpl implements ICode {
private String[] chars = {"赵", "钱", "孙", "李", "王", "五", "马", "六", "天", "地"};
@Override
public String makeCode() {
StringBuilder code = new StringBuilder();
Random random = new Random();
// 生成4位不重复的中文验证码
for (int i = 0; i < 4; ) {
String ch = chars[random.nextInt(chars.length)];
if (!code.toString().contains(ch)) {
code.append(ch);
i++; // 只有不重复时,才推进循环
}
}
return code.toString();
}
}
数字验证码实现(NumberCodeImpl)
java
package com.interfaces.impl;
import java.util.Random;
import com.interfacess.ICode;
/**
* 数字验证码实现类(服务提供者)
*/
public class NumberCodeImpl implements ICode {
private String[] nums = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
@Override
public String makeCode() {
StringBuilder code = new StringBuilder();
Random random = new Random();
for (int i = 0; i < 4; ) {
String num = nums[random.nextInt(nums.length)];
if (!code.toString().contains(num)) {
code.append(num);
i++;
}
}
return code.toString();
}
}
3.3 配置 SPI 文件
在resources/META-INF/services/目录下,创建名为com.interfacess.ICode的文件(文件名必须与接口全限定名一致),内容为实现类的全限定名:
- 文件路径必须是
META-INF/services/接口全限定名。 - 每行一个实现类的全限定名,
#开头的是注释。

3.4 使用ServiceLoader加载服务
创建工厂类CodeServiceFactory,通过ServiceLoader加载所有ICode实现类:
java
package com.service;
import java.util.Iterator;
import java.util.ServiceLoader;
import com.interfacess.ICode;
/**
* 验证码服务工厂:通过SPI加载实现类
*/
public class CodeServiceFactory {
public static String createCode(Class targetClass)
{
// 服务发现,是通过一个接口的策略文件来动态加载的,使用ServiceLoader加载所有ICode实现类
ServiceLoader s = ServiceLoader.load(targetClass);
// 遍历所有实现类
Iterator its = s.iterator();
ICode code = null;
while(its.hasNext())
{
// 把接口策略文件里的类的路径转换成ICode对象,获取实现类实例
code =(ICode)its.next();
}
String checkCode = code.makeCode(); // 生成验证码
return checkCode;
}
}
3.5 测试 SPI
java
package com.javaspi;
import com.interfacess.ICode;
import com.service.CodeServiceFactory;
public class Test
{
public static void main( String[] args )
{
while(true)
{
// 通过SPI加载ICode实现类,生成验证码
String checkCode = CodeServiceFactory.createCode(ICode.class);
System.out.println("获取的验证码为:"+checkCode);
try {
Thread.sleep(5000); // 每5秒生成一次
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
3.6 运行结果:(在运行期间更改了SPI 配置文件的注释)

3.7 核心代码解析
java
ServiceLoader s = ServiceLoader.load(targetClass);
- 作用:根据接口
targetClass,自动扫描META-INF/services/下的配置文件,加载所有实现类。 - 返回值:
ServiceLoader对象,可通过iterator()遍历所有实现类。
解耦的关键
- 接口
ICode与实现类ChineseCodeImpl/NumberCodeImpl完全分离。 - 切换实现类只需修改 SPI 配置文件,无需修改代码。
SPI 配置文件的 "魔法"
ServiceLoader会自动查找META-INF/services/下与接口全限定名一致的文件。- 文件内容中的每个实现类都会被反射实例化,无需硬编码
new XxxImpl()。