聊聊 Java SPI 机制

本文涉及的产品
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
简介: 聊聊 Java SPI 机制

大家好,我是不才陈某~

Java SPI 是基于接口的编程+策略模式+约定配置文件组合实现的动态加载机制,能够很方便的为某个接口寻找服务实现的机制。

今天这篇文章就来深入聊一下SPI。

什么是SPI?

SPI 全称:Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。

面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。

为了实现在模块装配的时候不用在程序里动态指明,这就需要一种服务发现机制。java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。这有点类似IOC的思想,将装配的控制权移到了程序之外。

这是一种JDK内置的一种服务发现的机制,用于制定一些规范,实际实现方式交给不同的服务厂商。如下图:

解耦、可拔插、面向接口编程、动态类加载。

当服务的提供者提供了一种接口的实现之后,需要在classpath下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。

当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader

SPI 的不足

  1. 不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
  2. 获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。(Spring 的BeanFactory,ApplicationContext 就要高级一些了。)
  3. 多个并发多线程使用 ServiceLoader 类的实例是不安全的。

API 与 SPI 区别?

API是调用并用于实现目标的类、接口、方法等的描述;

SPI是扩展和实现以实现目标的类、接口、方法等的描述;

换句话说,API 为操作提供特定的类、方法,SPI 通过操作来符合特定的类、方法。

SPI和API的使用场景解析:

  • API (Application Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。从使用人员上来说,API 直接被应用开发人员使用。
  • SPI (Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。

SPI 案例实现

下面来一个简单的案例实现:比如每个动物都有不同的叫声,作为声纹系统会定义一个接口,如下:

public interface AnimalSay {
    void say();
}

在这个系统中并没有实现具体的实现,但是在处理业务逻辑时有需要用到该实例,此时就需要用到SPI去加载实现类,定义一个AnimalManagerLoader,实现如下:

@Data
public class AnimalManagerLoader {
    private static final AnimalManagerLoader INSTANCE = new AnimalManagerLoader();
    private final List<AnimalSay> animalSays;
    private AnimalManagerLoader() {
        animalSays = load();
    }
    /**
     * 通过SPI加载实现类
     */
    private List<AnimalSay> load() {
        ArrayList<AnimalSay> animalSays = new ArrayList<>();
        Iterator<AnimalSay> iterator = ServiceLoader.load(AnimalSay.class).iterator();
        while (iterator.hasNext()){
            animalSays.add(iterator.next());
        }
        return animalSays;
    }
    public static AnimalManagerLoader getInstance() {
        return INSTANCE;
    }
}

此时就可以通过AnimalManagerLoader中的load方法去加载对应的实现类,封装到List集合中,调用如下:

public static void main(String[] args) {
        AnimalManagerLoader animalManagerLoader = AnimalManagerLoader.getInstance();
        List<AnimalSay> animalSays = animalManagerLoader.getAnimalSays();
        for (AnimalSay animalSay : animalSays) {
            animalSay.say();
        }
   }

那么此时提供声音的厂家就需要实现这个接口,比如狗狗的声纹厂家,实现如下:

/**
 * 狗狗的声纹
 */
public class DogSay implements AnimalSay {
    public void say() {
        System.out.println("wang wang ~");
    }
}

猫咪的声纹如下:

/**
 * 猫咪的声纹
 */
public class CatSay implements AnimalSay {
    @Override
    public void say() {
        System.out.println("miao miao ~");
    }
}

实现类定义了,就需要在 /META-INF/services 中定义一个 com.myjszl.animal.api.AnimalSay文件,内容如下:

com.myjszl.dog.api.DogSay
com.myjszl.dog.api.CatSay

SPI 应用场景

SPI扩展机制应用场景有很多,比如Common-Logging,JDBC,Dubbo、ShardingSphere等等。

1. JDBC场景

java中定义的java.sql.Driver接口,并没有具体的实现,实现方式而是交给不同的服务厂商:

  1. 在MySQL的jar包mysql-connector-java-6.0.6.jar中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。
  2. PostgreSQL的jar包PostgreSQL-42.0.0.jar中,也可以找到同样的配置文件,文件内容是org.postgresql.Driver,这是PostgreSQL对Java的java.sql.Driver的实现。

2. ShardingSphere场景

在ShardingSphere中为了实现分布式事务提供了一个接口ShardingTransactionManager,但是在其架构中并未对其做出具体的实现,而是交给不同的厂商去实现,比如JTA强一致性事务的XAShardingTransactionManager,在其中META-INF/services就有一个org.apache.shardingsphere.transaction.spi.ShardingTransactionManager文件,如下图:

以上只是简单的列举了几个场景,实际应用场景很多,比如Spring、Spring Boot 中都有用到SPI设计。

3. Spring 场景

Spring中大量使用了SPI;比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等

4. SLF4J 日志门面 场景

SLF4J加载不同提供商的日志实现类,比如log4j、log4j2、logback.....

总结

通过Java的SPI机制能够很方便的实现可插拔、解耦的功能设计,在日常的开发中要能想到该机制并能灵活的运用。

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
4天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
8天前
|
Java 编译器
探索Java中的异常处理机制
【10月更文挑战第35天】在Java的世界中,异常是程序运行过程中不可避免的一部分。本文将通过通俗易懂的语言和生动的比喻,带你了解Java中的异常处理机制,包括异常的类型、如何捕获和处理异常,以及如何在代码中有效地利用异常处理来提升程序的健壮性。让我们一起走进Java的异常世界,学习如何优雅地面对和解决问题吧!
|
29天前
|
存储 算法 Java
Java HashSet:底层工作原理与实现机制
本文介绍了Java中HashSet的工作原理,包括其基于HashMap实现的底层机制。通过示例代码展示了HashSet如何添加元素,并解析了add方法的具体过程,包括计算hash值、处理碰撞及扩容机制。
|
19天前
|
XML 安全 Java
Java反射机制:解锁代码的无限可能
Java 反射(Reflection)是Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性、方法和构造函数。 反射机制能够使程序具备更大的灵活性和扩展性
33 5
Java反射机制:解锁代码的无限可能
|
7天前
|
Java 数据库连接 开发者
Java中的异常处理机制及其最佳实践####
在本文中,我们将探讨Java编程语言中的异常处理机制。通过深入分析try-catch语句、throws关键字以及自定义异常的创建与使用,我们旨在揭示如何有效地管理和响应程序运行中的错误和异常情况。此外,本文还将讨论一些最佳实践,以帮助开发者编写更加健壮和易于维护的代码。 ####
|
13天前
|
安全 IDE Java
Java反射Reflect机制详解
Java反射(Reflection)机制是Java语言的重要特性之一,允许程序在运行时动态地获取类的信息,并对类进行操作,如创建实例、调用方法、访问字段等。反射机制极大地提高了Java程序的灵活性和动态性,但也带来了性能和安全方面的挑战。本文将详细介绍Java反射机制的基本概念、常用操作、应用场景以及其优缺点。 ## 基本概念 ### 什么是反射 反射是一种在程序运行时动态获取类的信息,并对类进行操作的机制。通过反射,程序可以在运行时获得类的字段、方法、构造函数等信息,并可以动态调用方法、创建实例和访问字段。 ### 反射的核心类 Java反射机制主要由以下几个类和接口组成,这些类
32 2
|
18天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
22 3
|
18天前
|
安全 Java UED
深入理解Java中的异常处理机制
【10月更文挑战第25天】在编程世界中,错误和意外是不可避免的。Java作为一种广泛使用的编程语言,其异常处理机制是确保程序健壮性和可靠性的关键。本文通过浅显易懂的语言和实际示例,引导读者了解Java异常处理的基本概念、分类以及如何有效地使用try-catch-finally语句来处理异常情况。我们将从一个简单的例子开始,逐步深入到异常处理的最佳实践,旨在帮助初学者和有经验的开发者更好地掌握这一重要技能。
19 2
|
20天前
|
Java 数据库连接 开发者
Java中的异常处理机制####
本文深入探讨了Java语言中异常处理的核心概念,通过实例解析了try-catch语句的工作原理,并讨论了finally块和throws关键字的使用场景。我们将了解如何在Java程序中有效地管理错误,提高代码的健壮性和可维护性。 ####
|
23天前
|
安全 Java 程序员
深入浅出Java中的异常处理机制
【10月更文挑战第20天】本文将带你一探Java的异常处理世界,通过浅显易懂的语言和生动的比喻,让你在轻松阅读中掌握Java异常处理的核心概念。我们将一起学习如何优雅地处理代码中不可预见的错误,确保程序的健壮性和稳定性。准备好了吗?让我们一起踏上这段旅程吧!
24 6