Skip to content

基于 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 机制 ​

  1. Spring Boot 通过 spring.factories 文件简化了 SPI 的使用。
  2. 开发者可以在 spring.factories 文件中注册服务提供者,Spring Boot 会在启动时自动加载这些提供者。
  3. 通过 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)

总结 ​

  1. SPI 是一种强大的机制,可以显著提升应用的扩展性和灵活性。
  2. 通过 SPI,开发者可以轻松地添加新的功能,而不需要修改现有代码。

Released under the MIT License.