从实现到原理,聊聊Java中的SPI动态扩展 下

简介: 从实现到原理,聊聊Java中的SPI动态扩展 下

4、服务发现

现在两个服务提供方都实现了接口,下面关键的一步就是服务发现,这一步 java 中的 spi 发现机制已经帮我们实现好了。

创建一个新项目aircondition-app,引入上面打好的两个 jar 包。

<dependencies>
    <dependency>
        <groupId>com.cn.hydra</groupId>
        <artifactId>aircondition-hanging-type</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>com.cn.hydra</groupId>
        <artifactId>aircondition-vertical-type</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

按照上面的说法,虽然每个服务提供者对于接口都有不同的实现,但是作为调用者来说,它并不需要关心具体的实现类,我们要做的是通过接口来调用服务提供者实现的方法。

下面,就是关键的服务发现环节,我们写一个方法,根据型号去调用对应空调的开关方法。

public class AirconditionApp {
    public static void main(String[] args) {
        new AirconditionApp().turnOn("VerticalType");
    }
    public void turnOn(String type){
        ServiceLoader<IAircondition> load = ServiceLoader
                .load(IAircondition.class);
        for (IAircondition iAircondition : load) {
            System.out.println("检测到:"+iAircondition.getClass().getSimpleName());
            if (type.equals(iAircondition.getType())){
                iAircondition.turnOnOff();
            }
        }
    }
}

测试结果:

可以看到,测试过程中,通过定义的接口IAircondition发现了两个实现类,并通过参数,调用了特定实现类的某个方法。整段代码中没有出现过具体的服务实现类,操作都是通过接口调用。

5、原理

了解了 spi 的工作流程,我们再来看看它的实现,其实最关键的就是上面代码中出现的ServiceLoader这个类。

上面的示例代码中,对于ServiceLoaderload()方法的结果,我们用for循环进行了遍历,这一点我们看一下源码就能明白,因为ServiceLoader实现了Iterable这一接口,而整个服务发现的核心,就在它的iterator()方法中。

注意这里面有两个关键的东西,找一下在源码中定义的地方:

image.png

注释写的非常明白,providers就是一个缓存,在迭代器中如果先从这里面进行查找,如果里面有就继续往下找,没有了的话就用这个懒加载的lookupIterator查找。

那么就简单了,接着往下看LazyIterator,看看它里面的hasNext()next()两个方法是怎么实现的。

这个acc是一个安全管理器,在前面通过System.getSecurityManager()判断并赋值,debug 看一下这里都是null,所以直接看hasNextService()nextService()方法就可以了。

hasNextService()方法中,会取出接口取出实现类的类名放到nextName中:

接下来,在nextService()方法中,则会先加载这个实现类,然后实例化对象,最终放入缓存中去。

在迭代器的迭代过程中,会完成所有实现类的实例化,其实归根结底,还是基于 java 反射去实现的。

6、应用

要说 spi 的实际应用,大家最常见的应该就是日志框架slf4j了,它利用 spi 实现了插槽式接入其他具体的日志框架。

说白了,slf4j本身就是个日志门面,并不提供具体的实现,需要绑定其他具体实现才能真正的引入日志功能。

例如我们可使用log4j2作为具体的绑定器,只需要在 pom 中引入slf4j-log4j12,就可以使用具体功能。

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.3</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>2.0.3</version>
</dependency>

引入项目后,点开它的 jar 包看一下具体结构:

有没有发现一个彩蛋,先说为什么我们 pom 中引入的明明是slf4j-log4j12,实际上引入的是slf4j-reload4j?翻一下官网的文档:

大意就是在 2015 年和 2022 年,log4j1.x就已经宣布end of life终止了,原因也不难猜,估计是因为频繁爆出的漏洞。在那之后,slf4j-log4j在构建阶段就会自动重定向到slf4j-reload4j了,并且官方也强烈建议使用slf4j-reload4j作为替代。

再回头看一下 jar 包的META-INF.services里面,通过 spi 注入了Reload4jServiceProvider这个实现类,它实现了SLF4JServiceProvider这一接口,在它的初始化方法initialize()中,会完成初始化等工作,后续可以继续获取到LoggerFactoryLogger等具体日志对象。

7、总结

Java 中的 SPI 提供了一种比较特别的服务发现和调用机制,通过接口灵活的将服务调用与服务提供者分离,用于提供给第三方实现扩展时还是很方便的。但是也有缺点,比方说一旦加载一个接口,就会把所有实现类都加载进来,可能会加载到不需要的冗余服务。不过站在整体角度上,还是给我们提供了一种非常不错的框架扩展、集成的思路。



相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
6月前
|
存储 缓存 Java
我们来详细讲一讲 Java NIO 底层原理
我是小假 期待与你的下一次相遇 ~
229 2
|
5月前
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
175 0
|
5月前
|
人工智能 安全 Java
Go与Java泛型原理简介
本文介绍了Go与Java泛型的实现原理。Go通过单态化为不同类型生成函数副本,提升运行效率;而Java则采用类型擦除,将泛型转为Object类型处理,保持兼容性但牺牲部分类型安全。两种机制各有优劣,适用于不同场景。
203 24
|
6月前
|
XML JSON Java
Java 反射:从原理到实战的全面解析与应用指南
本文深度解析Java反射机制,从原理到实战应用全覆盖。首先讲解反射的概念与核心原理,包括类加载过程和`Class`对象的作用;接着详细分析反射的核心API用法,如`Class`、`Constructor`、`Method`和`Field`的操作方法;最后通过动态代理和注解驱动配置解析等实战场景,帮助读者掌握反射技术的实际应用。内容翔实,适合希望深入理解Java反射机制的开发者。
591 13
|
5月前
|
存储 缓存 安全
深入讲解 Java 并发编程核心原理与应用案例
本教程全面讲解Java并发编程,涵盖并发基础、线程安全、同步机制、并发工具类、线程池及实际应用案例,助你掌握多线程开发核心技术,提升程序性能与响应能力。
247 0
|
6月前
|
算法 Java 索引
说一说 Java 并发队列原理剖析
我是小假 期待与你的下一次相遇 ~
|
7月前
|
人工智能 Java 关系型数据库
Java——SPI机制详解
SPI(Service Provider Interface)是JDK内置的服务提供发现机制,主要用于框架扩展和组件替换。通过在`META-INF/services/`目录下定义接口实现类文件,Java程序可利用`ServiceLoader`动态加载服务实现。SPI核心思想是解耦,允许不同厂商为同一接口提供多种实现,如`java.sql.Driver`的MySQL与PostgreSQL实现。然而,SPI存在缺陷:需遍历所有实现并实例化,可能造成资源浪费;获取实现类方式不够灵活;多线程使用时存在安全问题。尽管如此,SPI仍是Java生态系统中实现插件化和模块化设计的重要工具。
280 0
|
9月前
|
缓存 Dubbo Java
理解的Java中SPI机制
本文深入解析了JDK提供的Java SPI(Service Provider Interface)机制,这是一种基于接口编程、策略模式与配置文件组合实现的动态加载机制,核心在于解耦。文章通过具体示例介绍了SPI的使用方法,包括定义接口、创建配置文件及加载实现类的过程,并分析了其原理与优缺点。SPI适用于框架扩展或替换场景,如JDBC驱动加载、SLF4J日志实现等,但存在加载效率低和线程安全问题。
444 7
理解的Java中SPI机制
|
Dubbo Java 关系型数据库
Java SPI机制分析
文章深入分析了Java SPI机制,以JDBC为例,详细探讨了服务提供者接口的发现、加载过程,并提供了一个序列化服务的实战示例,展示了如何使用ServiceLoader进行服务发现和扩展。
109 3