Java进阶:利用SPI机制不侵入源码而实现定制功能【附带源码】

2024-08-29 08:44

本文主要是介绍Java进阶:利用SPI机制不侵入源码而实现定制功能【附带源码】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 0. 引言
  • 1. 什么是SPI
  • 2. SPI的优缺点
    • 2.1 优点
    • 2.2 缺点
  • 3. 应用场景
  • 4. 使用步骤
  • 5. 演示源码
  • 6. 总结

0. 引言

最近遇到一个场景,需要针对之前的文件上传工具包进行拓展,增加其他类型的文件服务器的操作代码,但之前的工具包因为是通用包在其他项目中也有使用,暂时不想去更改之前的包内容,于是想在不影响原代码的情况下,去实现拓展实现类,针对这个的需求,显然很适合java的SPI机制来实现,下面我们来看看具体如何操作。

1. 什么是SPI

首先我们要了解什么是SPI, (服务提供者接口 Service Provider Interface)是一种动态加载实现扩展点的机制,它允许服务提供者在运行时动态地为某个接口提供实现,而不需要在程序编译时进行硬编码。这种机制的核心思想是将装配的控制权移到程序之外,通过在模块化设计中实现接口与服务实现的解耦,从而提供一种插件化的扩展机制。SPI机制主要应用于框架和库的开发中,以支持服务的动态加载和替换

在Java中,SPI机制的实现依赖于java.util.ServiceLoader类,它负责查找和加载实现了特定接口的服务提供者。服务提供者需要在其JAR包的META-INF/services目录下创建一个以服务接口全限定名为名称的文件,并在该文件中列出实现该接口的具体类的名称。当应用程序需要使用某个服务时,可以通过ServiceLoader类查找并加载这些实现类,进而使用它们提供的服务

2. SPI的优缺点

2.1 优点

  • 松耦合

SPI 机制允许应用程序在运行时动态加载实现,客户端代码与具体实现解耦,提高了模块化程度和灵活性。

  • 动态扩展

可以在不修改现有代码的情况下,添加新的服务提供者。只需在配置文件中指定新的实现类即可,这使得应用程序可以方便地扩展功能,这也是使用spi的主要原因

  • 标准化

SPI 是 Java 平台的一部分,遵循标准规范,开发者可以利用它实现跨库的插件机制,增强代码的兼容性和可维护性。

  • 方便的服务加载

SPI 使用 ServiceLoader 类来加载服务提供者,简化了服务的加载过程,自动处理了实现类的查找和实例化。但加载虽然方便,使用起来仍然需要遍历所有的实现类,使用上也有不便之处。

2.2 缺点

  • 性能开销

SPI 在运行时动态加载实现类,这可能会引入一定的性能开销,特别是当服务提供者数量较多时,加载和实例化的过程可能较慢。另外java spi会在项目启动时就加载所有的实现类,而某些实现类如果暂时用不到但初始化耗时又很长时,就会增加初始化的整体耗时

  • 调试困难

由于 SPI 机制涉及动态加载和配置文件,调试可能会比较困难,错误信息不够直观,可能导致问题定位困难。

  • 管理复杂性

随着服务提供者数量的增加,管理和维护 SPI 配置文件可能会变得复杂,特别是在多个模块和库之间存在大量实现时。所以当我们的实现类很多时,要慎重考虑使用spi

  • 不支持泛型

SPI 机制不直接支持泛型,这意味着在定义服务接口时,不能使用泛型参数,可能需要采用其他设计模式或策略来解决。

  • 依赖配置文件

SPI 机制依赖于配置文件(通常是 META-INF/services/ 目录下的文件),如果配置文件不正确或缺失,会导致服务无法加载,增加了配置和部署的复杂度。这点在新增实现类时会经常遗漏

3. 应用场景

  • 插件架构

SPI 机制常用于实现插件架构,允许用户在运行时动态加载和卸载插件。应用程序可以通过 SPI 机制发现和加载插件,而不需要在编译时确定插件的具体实现。例如,很多大型应用程序和框架(如 Spring Boot、dubbo)都使用 SPI 来支持插件和扩展功能。

  • 数据库驱动加载

在 Java 数据库连接(JDBC)中,数据库驱动的加载就是一个典型的 SPI 应用场景。JDBC 驱动程序通过 SPI 机制注册自己,以便 Java 程序可以通过 DriverManager 动态加载并使用不同的数据库驱动,而无需在编译时指定具体的数据库实现。

  • 自定义功能拓展

针对一些工具包或者第三方包,我们希望拓展其功能,又不能改动其源码的情况,就可以借助SPI机制来进行扩展。

总之,Java SPI 机制在需要动态加载、解耦和扩展的场景中表现尤为出色,可以帮助构建灵活、可扩展的应用程序。

4. 使用步骤

1、而java SPI的使用实际上也很简单,首先我们先创建一个maven项目spi_demo_import,用来模拟我们要扩展的工具包,其项目下有接口类IFileService,以及原有的实现类ObsService

package com.example.file;import java.io.InputStream;public interface IFileService {String makeBucket(String bucketName);boolean existBucket(String bucketName);boolean removeBucket(String bucketName);boolean setBucketExpires(String bucketName, int days);void upload(String bucketName, String fileName, InputStream stream);
}public class ObsService implements IFileService{@Overridepublic String makeBucket(String bucketName) {return  "obs create " + bucketName + " bucket success";}@Overridepublic boolean existBucket(String bucketName) {// 具体的代码实现省略,仅演示return false;}@Overridepublic boolean removeBucket(String bucketName) {return false;}@Overridepublic boolean setBucketExpires(String bucketName, int days) {return false;}@Overridepublic void upload(String bucketName, String fileName, InputStream stream) {}
}

2、然后我们创建一个springboot项目spi_demo,我们在该项目中实现对工具包中IFileService接口的扩展,然后在spi_demo项目pom中引入刚刚创建的工具包项目spi_demo_import

3、我们计划扩展IFileService接口,实现对minio和oss对象存储的实现,创建MinioServiceOssService,这里省略具体的实现代码,仅做一个演示需要

public class OssService implements IFileService {@Overridepublic String makeBucket(String bucketName) {return "oss create " + bucketName + " bucket success";}@Overridepublic boolean existBucket(String bucketName) {return false;}@Overridepublic boolean removeBucket(String bucketName) {return false;}@Overridepublic boolean setBucketExpires(String bucketName, int days) {return false;}@Overridepublic void upload(String bucketName, String fileName, InputStream stream) {}
}public class MinioService implements IFileService {@Overridepublic String makeBucket(String bucketName) {return "minio create " + bucketName + " bucket success";}@Overridepublic boolean existBucket(String bucketName) {return false;}@Overridepublic boolean removeBucket(String bucketName) {return false;}@Overridepublic boolean setBucketExpires(String bucketName, int days) {return false;}@Overridepublic void upload(String bucketName, String fileName, InputStream stream) {}
}

4、其次在spi_demo项目中的resources资源目录下创建META-INF/services目录(注意这里是两个目录,先创建META-INF后再在其下创建services,否则可能会识别为一个目录)

5、再根据之前接口类IFileService的全包名来创建一个同名文本文件,如我这里是com.example.file.IFileService

6、然后在该文本中添加所有实现类的全包名,注意包括原工具包中的实现类

com.example.spi_demo.service.MinioService
com.example.spi_demo.service.OssService
com.example.file.ObsService

7、到这里我们的服务扩展就完成了,那么如何使用呢,我们创建一个controller,来模拟调用接口

如下可以看到其使用只需要通过ServiceLoader.load(IFileService.class)来获取所有实现类,然后遍历循环,通过instanceof来找到自己需要的具体实现类

@RestController
public class DemoController {@GetMapping("createBucket")public String createBucket(String name, Integer type){ServiceLoader<IFileService> serviceLoader = ServiceLoader.load(IFileService.class);IFileService fileService = null;for (IFileService service: serviceLoader){if(type == 0){if(service instanceof MinioService){fileService = service;}}else if(type == 1){if(service instanceof OssService){fileService = service;}}else {if(service instanceof ObsService){fileService = service;}}}return fileService.makeBucket(name);}
}

8、调用测试
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到不同的type参数成功切换到不同的实现类了,那么我们的扩展就成功了。

5. 演示源码

本文演示源码可在如下地址下载:

https://gitee.com/wuhanxue/wu_study/tree/master/demo/spi_demo

6. 总结

SPI的使用很简单,但是其性能上少有欠缺,实际使用时我们需要结合具体的情况来选择,千万不要涉及到扩展就无脑使用SPI, 选择最有效、最简单的方案。

这篇关于Java进阶:利用SPI机制不侵入源码而实现定制功能【附带源码】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1117399

相关文章

Springboot配置文件相关语法及读取方式详解

《Springboot配置文件相关语法及读取方式详解》本文主要介绍了SpringBoot中的两种配置文件形式,即.properties文件和.yml/.yaml文件,详细讲解了这两种文件的语法和读取方... 目录配置文件的形式语法1、key-value形式2、数组形式读取方式1、通过@value注解2、通过

Java 接口定义变量的示例代码

《Java接口定义变量的示例代码》文章介绍了Java接口中的变量和方法,接口中的变量必须是publicstaticfinal的,用于定义常量,而方法默认是publicabstract的,必须由实现类... 在 Java 中,接口是一种抽象类型,用于定义类必须实现的方法。接口可以包含常量和方法,但不能包含实例

JAVA Calendar设置上个月时,日期不存在或错误提示问题及解决

《JAVACalendar设置上个月时,日期不存在或错误提示问题及解决》在使用Java的Calendar类设置上个月的日期时,如果遇到不存在的日期(如4月31日),默认会自动调整到下个月的相应日期(... 目录Java Calendar设置上个月时,日期不存在或错误提示java进行日期计算时如果出现不存在的

C#实现将XML数据自动化地写入Excel文件

《C#实现将XML数据自动化地写入Excel文件》在现代企业级应用中,数据处理与报表生成是核心环节,本文将深入探讨如何利用C#和一款优秀的库,将XML数据自动化地写入Excel文件,有需要的小伙伴可以... 目录理解XML数据结构与Excel的对应关系引入高效工具:使用Spire.XLS for .NETC

Springboot的配置文件及其优先级说明

《Springboot的配置文件及其优先级说明》文章介绍了SpringBoot的配置文件,包括application.properties和application.yml的使用,以及它们的优先级,还讨... 目录配置文件内置配置文件yml与properties的比较优先级比较外置配置文件springboot

自定义注解SpringBoot防重复提交AOP方法详解

《自定义注解SpringBoot防重复提交AOP方法详解》该文章描述了一个防止重复提交的流程,通过HttpServletRequest对象获取请求信息,生成唯一标识,使用Redis分布式锁判断请求是否... 目录防重复提交流程引入依赖properties配置自定义注解切面Redis工具类controller

Nginx更新SSL证书的实现步骤

《Nginx更新SSL证书的实现步骤》本文主要介绍了Nginx更新SSL证书的实现步骤,包括下载新证书、备份旧证书、配置新证书、验证配置及遇到问题时的解决方法,感兴趣的了解一下... 目录1 下载最新的SSL证书文件2 备份旧的SSL证书文件3 配置新证书4 验证配置5 遇到的http://www.cppc

Nginx之https证书配置实现

《Nginx之https证书配置实现》本文主要介绍了Nginx之https证书配置的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起... 目录背景介绍为什么不能部署在 IIS 或 NAT 设备上?具体实现证书获取nginx配置扩展结果验证

Java利用Spire.XLS for Java自动化设置Excel的文档属性

《Java利用Spire.XLSforJava自动化设置Excel的文档属性》一个专业的Excel文件,其文档属性往往能大大提升文件的可管理性和可检索性,下面我们就来看看Java如何使用Spire... 目录Spire.XLS for Java 库介绍与安装Java 设置内置的 Excel 文档属性Java

Java中的CompletableFuture核心用法和常见场景

《Java中的CompletableFuture核心用法和常见场景》CompletableFuture是Java8引入的强大的异步编程工具,支持链式异步编程、组合、异常处理和回调,介绍其核心用法,通过... 目录1、引言2. 基本概念3. 创建 CompletableFuture3.1. 手动创建3.2.