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.