基于 Spring Boot SPI 机制轻松扩展应用功能
什么是 SPI
SPI(Service Provider Interface) 是一种服务发现机制,常用来启用框架扩展和替换组件。 比如 Dubbo, Spring,Sentinel 等开源框架均采用了 SPI 机制进行,提高了框架高度可扩展性。java.util.ServiceLoader 类是 Java 内置的 SPI 机制通过解析 classPath 和 jar 包的 META-INF/services/目录 下的以接口全限定名命名的文件,并加载该文件中指定的接口实现类,以此完成调用
Spring Boot 中的 SPI 机制
- Spring Boot 通过 spring.factories 文件简化了 SPI 的使用。
- 开发者可以在 spring.factories 文件中注册服务提供者,Spring Boot 会在启动时自动加载这些提供者。
- 通过 SpringFactoriesLoader 加载对应的实现类
需求场景
在数据与字节相互转化的需求中,我们提供默认的实现,同时也保留了可扩展,我们就可以通过 spi 机制来实现
定义需要 SPI 的服务接口
java
public interface Serializer {
<T> byte[] serialize(T obj);
<T> T deserialize(byte[] data, Class<T> clazz);
}
基于 JDK 实现
java
@Slf4j
public class JDKSerializer implements Serializer {
@Override
public <T> byte[] serialize(T obj) {
log.info("jdk 实现序列化");
if (obj == null) {
throw new RuntimeException("serialization obj is null");
}
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(os);
out.writeObject(obj);
return os.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) {
log.info("jdk 实现反序列化");
if (data == null) {
throw new RuntimeException("deserialize data is null");
}
try {
ByteArrayInputStream is = new ByteArrayInputStream(data);
ObjectInputStream in = new ObjectInputStream(is);
return (T) in.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
基于 jackson 实现
java
@Slf4j
public class JsonSerializer implements Serializer {
private static ObjectMapper objectMapper = new ObjectMapper();
static {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
objectMapper.setDateFormat(dateFormat);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
objectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET,false);
objectMapper.configure(JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT,false);
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
objectMapper.configure(JsonParser.Feature.IGNORE_UNDEFINED,true);
}
@Override
public <T> byte[] serialize(T obj) {
log.info("json 序列化");
if (obj == null) {
throw new RuntimeException("serialize object is null");
}
byte[] bytes = new byte[0];
try {
bytes = objectMapper.writeValueAsBytes(obj);
}catch (JsonProcessingException e) {
throw new RuntimeException(e.getMessage());
}
return bytes;
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) {
log.info("json 反序列化");
if (data == null) {
throw new RuntimeException("deserialize data is null");
}
T obj = null;
try {
obj = objectMapper.readValue(data,clazz);
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
return obj;
}
}
在 META-INF 下填相关接口信息,如下
xml
com.ssn.design.patterns.spi.Serializer=com.ssn.design.patterns.spi.JDKSerializer,com.ssn.design.patterns.spi.JsonSerializer
以接口全限定名作为 key,实现类作为 value 来配置,多个实现类用逗号隔开
测试
java
@Component
@Slf4j
public class SpiInit implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
List<Serializer> serializers = SpringFactoriesLoader.loadFactories(Serializer.class, Thread.currentThread().getContextClassLoader());
for (Serializer serializer : serializers) {
UserInfo userInfo = new UserInfo();
String name = "lcd";
userInfo.setUsername(name);
byte[] serialize = serializer.serialize(userInfo);
UserInfo deserialize = serializer.deserialize(serialize, UserInfo.class);
log.info("deserialize value :{}", deserialize);
}
}
}
输出内容如下
text
2024-11-07 11:13:39.119 INFO 26972 --- [ main] c.ssn.design.patterns.spi.JDKSerializer : jdk 实现反序列化
2024-11-07 11:13:39.120 INFO 26972 --- [ main] com.ssn.design.patterns.init.SpiInit : deserialize value :UserInfo(id=0, username=lcd, userPassword=null, createTime=null, userEmail=null)
2024-11-07 11:13:39.121 INFO 26972 --- [ main] c.s.design.patterns.spi.JsonSerializer : json 序列化
2024-11-07 11:13:39.168 INFO 26972 --- [ main] c.s.design.patterns.spi.JsonSerializer : json 反序列化
2024-11-07 11:13:39.201 INFO 26972 --- [ main] com.ssn.design.patterns.init.SpiInit : deserialize value :UserInfo(id=0, username=lcd, userPassword=null, createTime=null, userEmail=null)
总结
- SPI 是一种强大的机制,可以显著提升应用的扩展性和灵活性。
- 通过 SPI,开发者可以轻松地添加新的功能,而不需要修改现有代码。