Java SPI机制

一、软件版本:

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 设计理念是基于接口的编程(策略模式)+配置文件组合实现的动态加载机制

ae3f463cc7a04ae9affcd5121c9eebfa.png

4.2 API架构设置

8500e841adb14e708679405ede410932.png

 SPI的扩展是基于jar级别,API扩展是基于类级别

五 SPI 缺点

  1. Java SPI虽然使用了懒加载机制,但是其获取一个实现时,需要使用迭代器循环加载所有的实现类;
  2. 当需要某一个实现类时,需要通过循环一遍来获取;
  3. 多个并发多线程使用ServiceLoader类的实例是不安全的。

 

六 SPI 创建方式

30b44b5ec70342658466e956fe4cd1f4.png

 

七 SPI-MySql驱动

7.1 MySql驱动的配置22873431b015411fbdf79ebe35f2cb9f.png

  1. 序号1是定义文件夹目录
  2. 序号2是配置文件名称
  3. 序号3是配置文件内容

7.2 MySql驱动的实现类com.mysql.jdbc.Driver

c0795cd035d240369cbe804c5c50c8d0.png

 MySql驱动设置比较巧妙,加载类的时候通过调用类的static代码块来实现注册驱动。

 

7.3 调用入口在DriverManager类的getConnection方法里的ensureDriversInitialized()

1e58f4a647c040ef838be0fec285c08e.png

 7.4 ensureDriversInitialized方法实现SPI11bceb45757c4efda1f93ea334066096.png

 八 自定义SPI

 8.1 创建接口Service项目

8.1.1 创建cssca-message-service

 

 

c89cf27e1c7a40d7ad8ae09075dae6dc.png

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

52a26f49345440f1b61a041ff8570442.png

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

1d97e2f76b7543a983ca5422ab8d03a0.png

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

7af07684525941b5afee7b7e682a8a65.png

 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 运行结果

00dc80c4dda8468e9601ac379be0336d.png

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 运行测试类

e1b9f006e3b84e0fb2dabb2c9002688b.png

 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 运行结果

当有多个服务类时,程序获取的是最后一个服务类b5989e48a88c4816a502045590bfab33.png

九 总结

 

  1. 定义接口和发现服务在一个项目工程里,比如java.sql.Driver和java.sql.DriverManager;
  2. 发现服务类有多个时,需要特殊处理,java.sql.DriverManager的发现多个驱动时会跟url匹配,都匹配时取第一个驱动;
  3. 服务类的发现,我们可以采用获取直接获取对象或都采用注册方式,如message= messages.next(),DriverManager.registerDriver();
  4. 服务类发现不是线性安全的,需要多线程处理

 

 

### Java 中的 SPI 机制详解 #### 定义与作用 SPI 是 Service Provider Interface 的缩写,是 JDK 内置的一种服务发现机制。通过 SPI 可以使接口与其具体实现解耦合,在运行时动态加载指定的服务实现[^1]。 #### 工作原理 当应用程序启动时,JVM会扫描 `META-INF/services` 文件夹下的配置文件。对于每一个定义好的接口,都会有一个对应的配置文件来指明具体的实现类名称。例如,如果存在名为 `com.example.LoggerService` 接口,则应在资源路径下创建一个名为 `com.example.LoggerService` 的文件,并在此文件中记录该接口的具体实现类名[^2]。 #### 使用案例分析 考虑如下代码片段: ```java public class TestJavaSPI { public static void main(String[] args) { LoggerService loggerService = LoggerService.getService(); loggerService.info("你好"); loggerService.debug("测试Java SPI 机制"); } } ``` 上述例子展示了如何利用 SPI 加载并调用不同日志框架的日志功能。这里假设 `LoggerService` 是一个抽象出来的用于获取日志服务实例的方法;而实际执行的日志操作则由特定的日志库(比如 Logback)完成。因此可以看到控制台输出了来自Logback的信息级别和调试级别的消息。 #### 实现方式概述 要让某个接口支持 SPI 功能,开发者只需要遵循一定的约定即可: - 创建接口; - 在项目的 `resources/META-INF/services/` 下建立同包名相同命名规则的文字文件; - 文本文件内容即为对应接口实现者的全限定类名列表,每行一条记录[^3]。 #### 关键特性总结 - **灵活性**: 不必硬编码依赖关系,允许外部插件形式接入新的业务逻辑处理单元。 - **可维护性**: 减少了修改源码的需求,便于后期维护和技术栈升级迁移工作。 - **扩展性强**: 支持多版本共存和平滑切换不同的算法策略或工具链组件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值