Java的SPI机制

Java SPI(Service Provider Interface)是一种动态加载服务实现的机制,允许在运行时发现和加载接口的实现类。Dubbo在其基础上封装了一套更强的SPI,支持键值对配置和依赖注入。本文介绍了如何在Java中使用SPI,以及Dubbo SPI和IOC的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:

dubbo SPI官网:http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html

https://www.jianshu.com/p/46b42f7f593c

https://blog.csdn.net/djrm11/article/details/88695347

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

SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。

使用Java SPI需要遵循一些约定:

  • 当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
  • 接口实现类所在的jar包放在主程序的classpath中;
  • 主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
  • SPI的实现类必须携带一个不带参数的构造方法;

示例

1、首先,我们定义一个接口,名称为 Robot。

Robot.java

package com.example.demo.dao;

public interface Robot {
    void sayHello();
}

2、为Robot分别创建两个实现类:RobotImpl1和RobotImpl2。

RobotImpl1.java

package com.example.demo.service;

import com.example.demo.dao.Robot;

public class RobotImpl1 implements Robot {
    @Override
    public void sayHello() {
        System.out.println("This is RobotImpl1 !");
    }
}

RobotImpl2.java

package com.example.demo.service;

import com.example.demo.dao.Robot;

public class RobotImpl2 implements Robot {
    @Override
    public void sayHello() {
        System.out.println("This is RobotImpl2 !");
    }
}

3、META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名 com.example.demo.dao.Robot。文件内容为实现类的全限定的类名,如下:

4、为Robot接口创建测试用例。

RobotTest.java

package com.example.demo.dao;

import org.junit.Test;

import java.util.ServiceLoader;

public class RobotTest {

    @Test
    public void sayHello() {
        ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
        System.out.println("Java SPI Test");
        serviceLoader.forEach(Robot::sayHello);
    }
}

5、输出结果:

Java SPI Test
This is RobotImpl1 !
This is RobotImpl2 !

机制

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。

Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦

Java SPI的优点就是解耦,缺点也有。

缺点

  • 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
  • 多个并发多线程使用ServiceLoader类的实例是不安全的。

Dubbo SPI

Dubbo中也大量使用SPI的方式实现框架的扩展,不过它对java提供的原生SPI做了封装,重新实现了一套更强的SPI机制。Dubbo SPI的相关逻辑被封装在了ExtensionLoader类中,通过ExtensionLoader,我们加载指定的实现类。
与java SPI的实现类配置不同,Dubbo SPI是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。

上面的例子,我们使用Dubbo SPI实现了。

1、Robot接口。Robot接口使用@SPI注解标注下。

Robot.java

package com.example.demo.dao;

import com.alibaba.dubbo.common.extension.SPI;

@SPI
public interface Robot {
    void sayHello();
}

2、Robot两个实现类不变。

3、Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,并且它是通过键值对的方式配置实现类。

4、为Robot接口创建测试用例。

package com.example.demo.dao;

import com.alibaba.dubbo.common.extension.ExtensionLoader;
import org.junit.Test;

import java.util.ServiceLoader;

public class RobotTest {

    @Test
    public void sayHello() throws Exception {
        ExtensionLoader<Robot> extensionLoader =
            ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("RobotImpl1");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("RobotImpl2");
        bumblebee.sayHello();
    }
}

5、输出结果:

This is RobotImpl1 !
This is RobotImpl2 !

上面是Dubbo SPI的一个例子,除此之外,它可以对实现类实例的属性进行依赖注入,即IOC

Dubbo IOC

Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。
在上面代码中,objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值