SPI(Service Provider Interface)总结
SPI,即服务提供者接口,是一种特定的设计模式。它允许框架或核心库为第三方开发者提供一个预定义的接口,从而使他们能够为框架提供自定义的实现或扩展。
核心目标:
解耦:SPI机制让框架的核心与其扩展部分保持解耦,使核心代码不依赖于具体的实现。
动态加载:系统能够通过特定的机制(如Java的ServiceLoader)动态地发现和加载所需的实现。
灵活性:框架用户可以根据自己的需求选择、更换或增加新的实现,而无需修改核心代码。
可插拔:第三方提供的服务或实现可以轻松地添加到或从系统中移除,无需更改现有的代码结构。
价值:
为框架或库的用户提供更多的自定义选项和灵活性。
允许框架的核心部分保持稳定,同时能够容纳新的功能和扩展。
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(组件),分别是SmsService和EmailService。这些beans的创建取决于application.properties文件中特定的属性值。
@ConditionalOnProperty(name = “message.type”, havingValue = “sms”)
当application.properties或application.yml中定义的属性message.type的值为sms时,此条件为true。此时,smsService()方法将被调用,从而创建一个SmsService的bean。
@ConditionalOnProperty(name = “message.type”, havingValue = “email”)
当application.properties或application.yml中定义的属性message.type的值为email时,此条件为true。此时,emailService()方法将被调用,从而创建一个EmailService的bean。
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接口和两个实现(SmsService和EmailService)。然后,我们创建了一个自动配置类,其中包含两个bean定义,这两个bean定义分别基于application.properties中的属性值条件性地创建。在spring.factories文件中,我们声明了这个自动配置类,以便Spring Boot在启动时能够自动加载它。
评论区