一、软件版本:
Java: 17
二、SPI 概念
SPI的全称是Service Provider Interface,中文名为服务提供者接口,是一种服务发现机制,基于JDK内置的动态加载实现扩展点的机制(通过在 ClassPath 路径下的 META-INF/services 文件夹查找文件,自动加载文件里所定义的类),这一机制使得框架扩展和替换组件变得容易。
三、SPI术语
SPI由三个组件构成:Service、Service Provider、ServiceLoader
- Service:是一个公开的接口或抽象类,定义了一个抽象的功能模块(文件名称)
- Service Provider:是Service的实现类(文件内容)
- ServiceLoader:是SPI机制中的核心组件,负责在运行时发现并加载Service Provider
四、SPI架构和API架构区别
4.1 SPI架构
Java SPI 设计理念是基于接口的编程(策略模式)+配置文件组合实现的动态加载机制
4.2 API架构设置
SPI的扩展是基于jar级别,API扩展是基于类级别
五 SPI 缺点
- Java SPI虽然使用了懒加载机制,但是其获取一个实现时,需要使用迭代器循环加载所有的实现类;
- 当需要某一个实现类时,需要通过循环一遍来获取;
- 多个并发多线程使用ServiceLoader类的实例是不安全的。
六 SPI 创建方式
七 SPI-MySql驱动
7.1 MySql驱动的配置![22873431b015411fbdf79ebe35f2cb9f.png](https://img-blog.csdnimg.cn/22873431b015411fbdf79ebe35f2cb9f.png)
- 序号1是定义文件夹目录
- 序号2是配置文件名称
- 序号3是配置文件内容
7.2 MySql驱动的实现类com.mysql.jdbc.Driver
MySql驱动设置比较巧妙,加载类的时候通过调用类的static代码块来实现注册驱动。
7.3 调用入口在DriverManager类的getConnection方法里的ensureDriversInitialized()
7.4 ensureDriversInitialized方法实现SPI![11bceb45757c4efda1f93ea334066096.png](https://img-blog.csdnimg.cn/11bceb45757c4efda1f93ea334066096.png)
八 自定义SPI
8.1 创建接口Service项目
8.1.1 创建cssca-message-service
8.1.2 创建接口类服务接口类Message,并定义发送信息方法
package stu.cacss.service;
public interface Message {
/**
* 发送信息方法
*
* @param msg 信息
*/
void send(String msg);
}
8.1.3 创建获取实现服务的类
import java.util.Iterator;
import java.util.ServiceLoader;
public abstract class MessageFactory {
public static Message getMassage() {
Message message = null;
ServiceLoader<Message> serviceloader = ServiceLoader.load(Message.class);
Iterator<Message> messages = serviceloader.iterator();
while(messages.hasNext()) {
message= messages.next();
}
return message;
}
}
8.2 创建Email项目提供Provider
8.2.1 创建项目cssca-email-provider
8.2.2 引入Service接口包
<dependency>
<groupId>stu.sis</groupId>
<artifactId>cssca-message-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<optional>true</optional>
</dependency>
8.2.3 创建类Email实现接口Message
package stu.sis.impl;
import stu.cacss.service.Message;
public class Email implements Message {
/**
* 发送信息方法
*
* @param msg 信息
*/
public void send(String msg) {
System.out.println("email: " + msg);
}
}
8.3 创建Sms项目提供Provider
8.3.1 创建项目cssca-sms-provider
8.3.2 引入Service接口包
<dependency>
<groupId>stu.sis</groupId>
<artifactId>cssca-message-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<optional>true</optional>
</dependency>
8.3.3 创建类Sms实现接口Message
package stu.sis.impl;
import stu.cacss.service.Message;
public class Sms implements Message {
/**
* 发送信息方法
*
* @param msg 信息
*/
public void send(String msg) {
System.out.println("Sms: "+msg);
}
}
8.4 创建应用Send项目
8.4.1 创建项目cssca-message-send
8.4.2 pom.xml引入Sms的Jar包和Service的Jar包
<dependency>
<groupId>stu.sis</groupId>
<artifactId>cssca-message-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>stu.sis</groupId>
<artifactId>cssca-sms-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
8.4.3 创建测试类Send
package stu.sis;
import stu.cacss.service.Message;
import stu.cacss.service.MessageFactory;
public class Send {
public static void main(String[] args){
Message message = MessageFactory.getMassage();
message.send("Hello World!");
}
}
8.4.4 运行结果
8.4.5 换成Email的Jar包导入
成Email的Jar包导入
<dependency>
<groupId>stu.sis</groupId>
<artifactId>cssca-message-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>stu.sis</groupId>
<artifactId>cssca-email-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
8.4.6 运行测试类
8.4.7 同时导入两个jar
<dependency>
<groupId>stu.sis</groupId>
<artifactId>cssca-message-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>stu.sis</groupId>
<artifactId>cssca-email-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>stu.sis</groupId>
<artifactId>cssca-sms-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
8.4.8 运行结果
当有多个服务类时,程序获取的是最后一个服务类
九 总结
- 定义接口和发现服务在一个项目工程里,比如java.sql.Driver和java.sql.DriverManager;
- 发现服务类有多个时,需要特殊处理,java.sql.DriverManager的发现多个驱动时会跟url匹配,都匹配时取第一个驱动;
- 服务类的发现,我们可以采用获取直接获取对象或都采用注册方式,如message= messages.next(),DriverManager.registerDriver();
- 服务类发现不是线性安全的,需要多线程处理