什么是 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 的服务接口
public interface Serializer {<T> byte[] serialize(T obj);<T> T deserialize(byte[] data, Class<T> clazz);}
基于 JDK 实现
@Slf4j
public class JDKSerializer implements Serializer {@Overridepublic <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());}}@Overridepublic <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 实现
@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);}@Overridepublic <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;}@Overridepublic <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 下spring.factories填相关接口信息,如下
com.ssn.design.patterns.spi.Serializer=com.ssn.design.patterns.spi.JDKSerializer,com.ssn.design.patterns.spi.JsonSerializer
以接口全限定名作为 key,实现类作为 value 来配置,多个实现类用逗号隔开。
测试
@Component
@Slf4j
public class SpiInit implements CommandLineRunner {@Overridepublic 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);}}
}
输出内容如下
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,开发者可以轻松地添加新的功能,而不需要修改现有代码。