❓你有没有遇到过这样的时刻 用着别人家的三方接口(API),写代码就像“饭店吃饭”——菜单一摸,上来就点,根本不用关心后厨大妈怎么做的。只要文档写明“调我、给你食物”,我就知道:我管你咋弄的,反正调用了就能拿到我想要的东西(食物)!
直到某天你突然发现:“咦?怎么这 API 这么慢?!”
一行日志一查,源码一翻:某个实现就像一个摸鱼的厨师,效率远低于你的预期。你知道哪里有好厨师,知道怎么优化,但你总不能直接对老板说:兄弟,把厨师给我换了,这 API 真不行。
因为 Java 的设计思想是面向扩展开放,面向修改关闭的
这时候,业界大神们灵机一动:既然不能直接动后厨,那不如让大家都能带自己的厨师来公共厨房客串——于是 SPI(Service Provider Interface)机制横空出世!
API(Application Programming Interface)
应用程序编程接口 ,是一组规则和定义,允许一个软件应用与另一个软件应用、库或操作系统进行交互。它定义了如何进行数据传输、请求服务或执行特定功能的协议和工具。API为开发人员提供了一种标准化的方式来访问和使用预先构建的功能,而无需了解这些功能内部的复杂实现细节。
SPI(Service Provider Interface)
服务提供者接口,是一种用于发现和加载服务实现的机制。它允许开发者通过定义接口并由不同的提供商实现这些接口的方式扩展功能。Java 提供了内置的支持来处理 SPI 机制。
1. Java 中的 SPI 实现方式
Java 标准 SPI 机制的核心在于 java.util.ServiceLoader 类。通过 ServiceLoader,开发者可以自动发现、加载和实例化指定接口的所有服务实现。
ServiceLoader<T> ServiceLoader.load(Class<T> service)该静态方法接受一个接口(通常称为“服务接口”或 SPI 接口)作为参数,返回一个
ServiceLoader<T>实例。其类中扫描路径前缀
String PREFIX = “META-INF/services/”;此方法会扫描类路径下所有 JAR 包中的
META-INF/services/接口全限定名文件,并实例化这些文件中声明的实现类。
SPI 主要依赖于 META-INF/services 路径下的配置文件。该路径下会有一个以接口全限定名命名的文件,其中每一行指定一个具体的实现类名称。当调用 java.util.ServiceLoader.load() 方法时,JVM 将自动扫描此目录中的配置文件,并实例化对应的实现类。
具体的操作流程:
- 定义一个公共接口。
- 创建多个实现该接口的具体类。
- 在资源目录
src/main/resources/META-INF/services下创建一个名为<interface fully qualified name>的文件。 - 在上述文件中每行写入一个实现类的全限定名。
- 使用
ServiceLoader加载所有可用的服务实现。
2. 示例demo
以下是一个简单的例子说明如何利用 SPI 来动态加载序列化器。
2.1 开发者定义序列化接口
package com.flux_rpc.core.serializer;
import com.flux_rpc.core.exception.SerializeException;
/**
* 序列化接口
* 用于将 Java 对象序列化为字节数组,反之亦然
* 支持多种实现:JSON、Hessian、Protobuf 等
*/
public interface Serializer {
/**
* 序列化:Java 对象 → 字节数组
*
* @param obj 要序列化的对象
* @return 序列化后的字节数组
* @throws SerializeException 序列化异常
*/
byte[] serialize(Object obj) throws SerializeException;
/**
* 反序列化:字节数组 → Java 对象
*
* @param bytes 字节数组
* @param clazz 目标类型
* @return 反序列化后的对象
* @throws SerializeException 反序列化异常
*/
<T> T deserialize(byte[] bytes, Class<T> clazz) throws SerializeException;
/**
* 获取序列化器编码
* 用于 RPC 协议中的 Serializer 字段
*
* @return 编码值(0x01、0x02、0x03)
*/
byte getSerializerCode();
/**
* 获取序列化器名称
*
* @return 名称("json"、"hessian"、"protobuf" )
*/
String getSerializerName();
}2.2 用户自定义序列化实例
package com.flux_rpc.demo.serializer;
import com.flux_rpc.core.exception.SerializeException;
import com.flux_rpc.core.serializer.Serializer;
import java.util.Arrays;
public class MyTestSerializer implements Serializer {
@Override
public byte[] serialize(Object obj) throws SerializeException {
System.out.println("rx_serialize " + obj);
return new byte[0];
}
@Override
public <T> T deserialize(byte[] bytes, Class<T> clazz) throws SerializeException {
System.out.println("rx_deserialize " + Arrays.toString(bytes));
return null;
}
@Override
public byte getSerializerCode() {
return 0x04;
}
@Override
public String getSerializerName() {
return "rx_serializer";
}
}
2.3 META-INF 配置
在 src/main/resources/META-INF/services/com.flux_rpc.core.serializer.Serializer 文件中添加如下内容:
com.flux_rpc.demo.serializer.MyTestSerializer❗开发者定义的序列化接口与用户 META-INF 配置不在同一个模块下,只要限定名正确,都在classpath上,即使Serializer文件报红:
无法解析符号 ‘com. flux_rpc. demo. serializer. MyTestSerializer’
也可以正常获取到序列化器(但是这并不意味着推荐你在自己的包中去声明其他人的类)
SPI 查找所有依赖及自身 classpath 下 META-INF/services,不区分是 core 还是用户扩展包
2.4 SerializerFactory 的 SPI 加载
重点看加载序列化器实例部分
/**
* 加载序列化器实例
*/
private static void loadSerializersViaSpi() {
ServiceLoader<Serializer> loader = ServiceLoader.load(Serializer.class);
for (Serializer serializer : loader) {
registerSerializer(serializer);
}
}整个工厂的代码如下
package com.flux_rpc.core.serializer;
import com.flux_rpc.common.protocol.SerializerType;
import com.flux_rpc.core.exception.SerializeException;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
/**
* 序列化器工厂
* 负责序列化器的注册和获取
*/
@Slf4j
public class SerializerFactory {
// 编码到 Serializer 的映射
private static final Map<Byte, Serializer> codeToSerializer = new ConcurrentHashMap<>();
// 名称到 Serializer 的映射
private static final Map<String, Serializer> nameToSerializer = new ConcurrentHashMap<>();
// 默认序列化器
private static final Serializer defaultSerializer;
static {
// SPI 加载
loadSerializersViaSpi();
// 注册所有内置实现
registerSerializer(new JsonSerializer());
registerSerializer(new ProtobufSerializer());
registerSerializer(new HessianSerializer());
// 设置默认序列化器
defaultSerializer = nameToSerializer.get(SerializerType.JSON.getName());
log.info("{} Serializers have been registered, default serializer: {} will be used",
codeToSerializer.size(), defaultSerializer.getSerializerName());
}
private SerializerFactory() throws SerializeException {
throw new SerializeException("SerializerFactory is a utility class");
}
/**
* 注册序列化器实例
*
* @param serializer 目标类型
*/
public static void registerSerializer(Serializer serializer) {
codeToSerializer.put(serializer.getSerializerCode(), serializer);
nameToSerializer.put(serializer.getSerializerName().toLowerCase(), serializer);
}
/**
* 加载序列化器实例
*/
private static void loadSerializersViaSpi() {
ServiceLoader<Serializer> loader = ServiceLoader.load(Serializer.class);
for (Serializer serializer : loader) {
registerSerializer(serializer);
}
}
/**
* 按编码获取序列化器
*
* @param code 编码
* @return 对应的序列器
* @throws SerializeException 序列化异常
*/
public static Serializer getSerializer(byte code) throws SerializeException {
Serializer serializer = codeToSerializer.get(code);
if (serializer == null) {
throw new SerializeException("Unknown serializer code: 0x" + String.format("%02X", code));
}
return serializer;
}
/**
* 按名称获取序列化器
*
* @param name 名称
* @return 对应的序列器
* @throws SerializeException 序列化异常
*/
public static Serializer getSerializer(String name) throws SerializeException {
if (name == null) return defaultSerializer;
Serializer serializer = nameToSerializer.get(name.toLowerCase());
if (serializer == null) {
throw new SerializeException("Unknown serializer name: " + name);
}
return serializer;
}
/**
* 获取默认序列化器
*
* @return 默认序列化器
*/
public static Serializer getDefaultSerializer() {
return defaultSerializer;
}
/**
* 获取所有已注册的序列化器(只读)
*
* @return 序列化器列表(只读)
*/
public static Map<String, Serializer> getAllSerializers() {
return Map.copyOf(nameToSerializer);
}
}2.5 测试
package core.serializer;
import com.flux_rpc.core.serializer.Serializer;
import com.flux_rpc.core.serializer.SerializerFactory;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class SpiSerializerFactoryTest {
@Test
public void testSpiCustomSerializerLoaded() throws Exception {
// 用名字获取
Serializer serializer = SerializerFactory.getSerializer("rx_serializer");
assertNotNull(serializer, "自定义序列化器未加载!");
assertEquals("rx_serializer", serializer.getSerializerName());
// 用code获取
Serializer serializerByCode = SerializerFactory.getSerializer((byte)0x04);
assertNotNull(serializerByCode, "自定义序列化器未加载!");
assertEquals(serializer, serializerByCode);
// 行为验证
serializer.serialize("test object");
serializer.deserialize(new byte[]{1,2,3}, Object.class);
}
}控制台运行结果如下:
15:33:14.569 [main] INFO com.flux_rpc.core.serializer.SerializerFactory - 4 Serializers have been registered, default serializer: json will be used
rx_serialize test object
rx_deserialize [1, 2, 3]
进程已结束,退出代码为 0❓为什么是4个序列化器而不是1个
- 因为代码实现中,默认自带三个序列化器:hessian、protobuf、json
3. Java中还有哪些SPI实现?
1、JDBC(Java Database Connectivity)
只需把比如 MySQL、PostgreSQL 等数据库厂商的 JDBC 驱动 JAR 扔进类路径,就能自动连数据库。为什么?因为每个驱动都注册了自己的“SPI身份”,Java一启动就能在幕后发现它们——无需在代码里点名“我要用哪个”,自动上岗
2、日志框架
比如 SLF4J、Logback、Log4j,它们都玩出了”可插拔日志实现”的花样。你只管 import SLF4J 的门面接口,至于到底用 Logback 还是 Log4j,只要你把实现的 JAR 包加进来,SPI 就能自动切换——想换日志实现不用动业务代码
3、Spring 框架
Spring 的 bean 工厂和依赖注入机制更强大,但 SPI,它也用。Spring 各种 Starter、自动配置和扩展点(比如各种 EventListener、FactoryLoader),底下就是 SPI 的套路
4、Dubbo
Dubbo 把序列化、负载均衡、集群容错、协议扩展……全部都做成 SPI。你要自定义序列化、定制负载算法、搞自己的注册中心?只要实现 SPI 接口!
