Java中的SPI机制使用解析

 更新时间:2023年10月07日 08:57:05   作者:cloneme01  
这篇文章主要介绍了Java中的SPI机制使用解析,SPI意思是"服务提供者的接口",专门提供给服务提供者或者扩展框架功能的开发者去使用的接口,SPI 将服务接口和服务实现分离开来,将服务调用方和服务实现方进行解耦,需要的朋友可以参考下

SPI机制简述

SPI(Service Provider Interface的缩写) 意思是:“服务提供者的接口”,专门提供给服务提供者或者扩展框架功能的开发者去使用的接口。SPI 将服务接口和服务实现分离开来,将服务调用方和服务实现方进行解耦,能够提升程序的扩展性和可维护性,当修改或替换服务实现并不需要修改调用方。 SPI 是JDK内置的一种动态加载扩展点的实现。

场景

很多框架都使用了JAVA 的 SPI 机制,如:数据库加载驱动、日志接口以及DUBBO的扩展实现等。

SPI与API的区别

(1)API(Application Programming Interface的缩写),一般都是由实现方制定接口并完成对接口的实现,调用方仅依赖接口调用,并无法选择不同实现。

(2)SPI(Service Provider Interface的缩写),一般都是由调用方来制定接口规范,将接口规范提供给外部厂商来实现,调用方在调用时可以选择不同的实现。

在这里插入图片描述

SPI的实现

(1)定义一个公共接口(一般定义在一个公共的模块中,实现方和调用方都会引用该模块);

(2)实现方需要实现刚刚定义的公共接口;

(3)实现方在“META-INF/services”目录下新建一个名称为接口的全限定名的文本文件,文件内容为接口实现类的全限定名(一般会在resources目录下创建如上文件);

(4)调用方通过ServiceLoader#load方法加载接口的实现类实例。

SPI的示例代码

在“模块1”中定义一个公共接口“Hello”,分别在“模块2”和“模块2”两个模块中实现刚刚定义的接口作为两个实现方,在“模块4”中通过ServiceLoader进行测试并作为调用方。

// 1. 公共接口(模块1):
package com.hadoopx.common.util;
public interface Hello {
    void say();
}
// 2. 具体实现1(模块2):
package com.hadoopx.m1.test;
import com.hadoopx.common.util.Hello;
public class Chinese implements Hello {
    @Override
    public void say() {
        System.out.println("你好!");
    }
}
// 3. 创建文件: resources/META-INF/services/com.hadoopx.common.util.Hello,内容如下:
com.hadoopx.m1.test.Chinese
// 4. 具体实现2(模块3):
package com.hadoopx.m2.test;
import com.hadoopx.common.util.Hello;
public class English implements Hello {
    @Override
    public void say() {
        System.out.println("HELLO WORLD!");
    }
}
// 5. 创建文件: resources/META-INF/services/com.hadoopx.common.util.Hello,内容如下:
com.hadoopx.m2.test.English
// 6. 调用方(模块4):
package com.hadoopx.test;
import com.hadoopx.common.util.Hello;
import java.util.Iterator;
import java.util.ServiceLoader;
public class Test {
    public static void main(String[] args) {
        ServiceLoader<Hello> loader = ServiceLoader.load(Hello.class);
        Iterator<Hello> iterator = loader.iterator();
        while (iterator.hasNext()) {
            Hello hello = iterator.next();
            hello.say();
        }
    }
}

JAVA与SPRING关于SPI的区别

SPRING SPI对JAVA SPI进行了封装和增强,调用方可以使用SpringFactoriesLoader.loadFactories方法得到配置文件中写的实现类的集合,其要求如下:

(1)配置文件必须在resources/META-INF/目录下,文件名必须为spring.factories;

(2)文件内容为键值对形式,一个键可以有多个值(需要用逗号分割),键值都要求是类的全限定名,键名为接口的全限定名。

在SPRING BOOT的自动装配过程中,最终会加载META-INF/spring.factories文件,加载的过程是由SpringFactoriesLoader加载的,它会从CLASSPATH下的每个JAR包中搜寻所有META-INF/spring.factories配置文件并解析,然后将其中定义的BEAN注入到SPRING容器。

// 1. 公共接口(模块1):
package com.hadoopx.common.util;
public interface Hello {
    void say();
}
// 2. 具体实现1(模块2):
package com.hadoopx.m1.test;
import com.hadoopx.common.util.Hello;
public class Chinese implements Hello {
    @Override
    public void say() {
        System.out.println("你好!");
    }
}
// 3. 创建/修改文件: resources/META-INF/spring.factories,内容如下:
com.hadoopx.common.util.Hello = \
com.hadoopx.m1.test.Chinese
// 4. 具体实现2(模块3):
package com.hadoopx.m2.test;
import com.hadoopx.common.util.Hello;
public class English implements Hello {
    @Override
    public void say() {
        System.out.println("HELLO WORLD!");
    }
}
// 5. 创建/修改文件: resources/META-INF/spring.factories,内容如下:
com.hadoopx.common.util.Hello = \
com.hadoopx.m2.test.English
// 6. 调用方(模块4):
package com.hadoopx.test;
import com.hadoopx.common.util.Hello;
import java.util.Iterator;
import java.util.ServiceLoader;
public class Test {
    public static void main(String[] args) {
        List<Hello> hellos = SpringFactoriesLoader.loadFactories(Hello.class, null);
        for(Hello hello: hellos) {
            hello.say();
        };
    }
}

SPI的缺点

(1)ServiceLoader会遍历加载所有的实现类,效率相对较低;

(2)当由多个ServiceLoader同时加载时,会存在并发的问题。

到此这篇关于Java中的SPI机制使用解析的文章就介绍到这了,更多相关SPI机制使用详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 基于@JsonSerialize和@JsonInclude注解使用方法

    基于@JsonSerialize和@JsonInclude注解使用方法

    这篇文章主要介绍了@JsonSerialize和@JsonInclude注解使用方法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Spring整合Dubbo框架过程及原理解析

    Spring整合Dubbo框架过程及原理解析

    这篇文章主要介绍了Spring整合Dubbo框架过程及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • springboot捕获全局异常实现过程

    springboot捕获全局异常实现过程

    本文主要介绍了Java中的异常和错误,包括Exception和Error的区别、如何捕捉全局异常、自定义异常的实现等,通过实例代码和步骤,展示了如何在Spring Boot项目中实现全局异常处理,并自定义异常类来增强程序的健壮性
    2026-03-03
  • 使用MyBatis 动态update数据

    使用MyBatis 动态update数据

    使用mybatis写sql,需要动态更新对象数据,每次需要更新的字段不同,为了防止null空异常,就需要用动态sql了,下面给大家分享一段代码关于mybatis动态update,需要的朋友参考下
    2016-11-11
  • Java中过滤器和拦截器的区别有哪些

    Java中过滤器和拦截器的区别有哪些

    过滤器和拦截器非常相似,但是它们有很大的区别,这篇文章主要介绍了Java中过滤器和拦截器的区别有哪些的相关资料,文中介绍的非常详细,需要的朋友可以参考下
    2025-09-09
  • java判断一个文件是否为二进制文件的方法

    java判断一个文件是否为二进制文件的方法

    这篇文章主要介绍了java判断一个文件是否为二进制文件的方法,涉及java针对文件的读取及编码判断技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • java org.springframework.boot 对redis操作方法

    java org.springframework.boot 对redis操作方法

    在Spring Boot项目中操作Redis,你可以使用Spring Data Redis,Spring Data Redis是Spring提供的一个用于简化Redis数据访问的模块,它提供了一个易于使用的编程模型来与Redis交互,本文给大家介绍java org.springframework.boot 对redis操作方法,感兴趣的朋友一起看看吧
    2025-04-04
  • 浅谈SpringBoot自动配置的坑

    浅谈SpringBoot自动配置的坑

    SpringBoot的自动配置简化了开发,但也会带来一些问题,本文通过几个实际案例探讨了一些自动配置可能带来的问题,下面就来详细的介绍下
    2026-05-05
  • redisson常用加锁方式详解

    redisson常用加锁方式详解

    文章介绍了两种Redisson加锁方式的区别:基本的RLock方式和使用RedissonDistributedLocker的tryLock方式,RLock需要手动处理锁的等待和持有时间,而tryLock则提供了更高层次的封装,简化了锁的操作,并且在超时后会自动返回或释放锁
    2026-01-01
  • Java微信授权登陆的实现示例

    Java微信授权登陆的实现示例

    微信授权登录,官方文档写的比较简洁。所以对于会的人一目了然,而对于新手刚入门的人来说是举步维艰。本文详细的介绍了Java微信授权登陆的实现示例,感兴趣的朋友可以了解一下
    2021-06-06

最新评论