侧边栏壁纸
博主头像
aixiao博主等级

行动起来,活在当下

  • 累计撰写 3 篇文章
  • 累计创建 1 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

SPI

shiyu
2023-11-12 / 0 评论 / 1 点赞 / 5 阅读 / 13545 字

SPI(Service Provider Interface)总结

SPI,即服务提供者接口,是一种特定的设计模式。它允许框架或核心库为第三方开发者提供一个预定义的接口,从而使他们能够为框架提供自定义的实现或扩展。

核心目标:

  • 解耦:SPI机制让框架的核心与其扩展部分保持解耦,使核心代码不依赖于具体的实现。

  • 动态加载:系统能够通过特定的机制(如JavaServiceLoader)动态地发现和加载所需的实现。

  • 灵活性:框架用户可以根据自己的需求选择、更换或增加新的实现,而无需修改核心代码。

  • 可插拔:第三方提供的服务或实现可以轻松地添加到或从系统中移除,无需更改现有的代码结构。

价值:

  • 为框架或库的用户提供更多的自定义选项和灵活性。

  • 允许框架的核心部分保持稳定,同时能够容纳新的功能和扩展。

SPI与“开闭原则”:

“开闭原则”提倡软件实体应该对扩展开放,但对修改封闭。即在不改变现有代码的前提下,通过扩展来增加新的功能。

SPI如何体现“开闭原则”:

  • 对扩展开放:SPI提供了一种标准化的方式,使第三方开发者可以为现有系统提供新的实现或功能。

  • 对修改封闭:添加新的功能或特性时,原始框架或库的代码不需要进行修改。

  • 独立发展:框架与其SPI实现可以独立地进化和发展,互不影响。

总之,SPI是一种使软件框架或库更加模块化、可扩展和可维护的有效方法。通过遵循“开闭原则”,SPI确保了系统的稳定性和灵活性,从而满足了不断变化的业务需求。

Java SPI的具体约定

当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件,该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。

3.1 SPI 接口

首先,需要定义一个 SPI 接口,和普通接口并没有什么差别。

package io.github.dunwu.javacore.spi;

public interface DataStorage {
    String say(String key);
}

3.2 SPI 实现类

假设,我们需要在程序中使用两种不同的数据存储——MySQL 和 Redis。因此,我们需要两个不同的实现类去分别完成相应工作。

MySQL查询 MOCK 类:

package io.github.dunwu.javacore.spi;

public class MysqlStorage implements DataStorage {
    @Override
    public String say(String key) {
        return "【Mysql】";
    }
}

Redis 查询 MOCK 类:

package io.github.dunwu.javacore.spi;

public class RedisStorage implements DataStorage {
    @Override
    public String say(String key) {
        return "【Redis】";
    }
}

service 传入的是期望加载的 SPI 接口类型 到目前为止,定义接口,并实现接口和普通的 Java 接口实现没有任何不同。

3.3 SPI 配置

如果想通过 Java SPI 机制来发现服务,就需要在 SPI 配置中约定好发现服务的逻辑。配置文件必须置于 META-INF/services 目录中,并且,文件名应与服务提供者接口的完全限定名保持一致。文件中的每一行都有一个实现服务类的详细信息,同样是服务提供者类的完全限定名称。以本示例代码为例,其文件名应该为
io.github.dunwu.javacore.spi.DataStorage,

文件中的内容如下:

com.demo.javacore.spi.MysqlStorage com.demo.javacore.spi.RedisStorage

spring-boot中 spi

src/main/resources/META-INF目录下spring.factories文件

package com.example.demo.service.impl;

import com.example.demo.service.MessageService;

public class EmailService implements MessageService {
    @Override
    public void send(String message) {
        System.out.println("Sending Email: " + message);
    }
}

自动配置类:

package com.example.demo.configuration;

import com.example.demo.service.EmailService;
import com.example.demo.service.MessageService;
import com.example.demo.service.SmsService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MessageAutoConfiguration {

    @Bean
    @ConditionalOnProperty(name = "message.type", havingValue = "sms")
    public MessageService smsService() {
        return new SmsService();
    }

    @Bean
    @ConditionalOnProperty(name = "message.type", havingValue = "email")
    public MessageService emailService() {
        return new EmailService();
    }
}

这个类提供两个条件性的beans(组件),分别是SmsServiceEmailService。这些beans的创建取决于application.properties文件中特定的属性值。

  • @ConditionalOnProperty(name = “message.type”, havingValue = “sms”)

application.propertiesapplication.yml中定义的属性message.type的值为sms时,此条件为true。此时,smsService()方法将被调用,从而创建一个SmsServicebean

  • @ConditionalOnProperty(name = “message.type”, havingValue = “email”)

application.propertiesapplication.yml中定义的属性message.type的值为email时,此条件为true。此时,emailService()方法将被调用,从而创建一个EmailServicebean

spring.factories文件:

src/main/resources/META-INF目录下创建一个spring.factories文件,内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.demo.configuration.MessageAutoConfiguration

application.properties文件:

message.type=sms

MessageTester组件:

package com.example.demo;

import com.example.demo.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class MessageTester {

    @Autowired
    private MessageService messageService;

    @PostConstruct
    public void init() {
        messageService.send("Hello World");
    }
}

DemoApplication主程序:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

运行结果:

在上述例子中,我们创建了一个MessageService接口和两个实现(SmsServiceEmailService)。然后,我们创建了一个自动配置类,其中包含两个bean定义,这两个bean定义分别基于application.properties中的属性值条件性地创建。在spring.factories文件中,我们声明了这个自动配置类,以便Spring Boot在启动时能够自动加载它。

1

评论区