详解java实践SPI机制及浅析源码

详解 Java 实践 SPI 机制及浅析源码

什么是 SPI 机制

SPI(Service Provider Interface)即服务提供者接口,是一种动态替换服务实现的机制。在 SPI 机制中,服务接口的实现必须和接口分离,并通过配置文件声明其实现类。

如何使用 SPI 机制

Java SPI 机制基于 Java 的类加载机制实现。以 java.util.ServiceLoader 为例,Java SPI 主要涉及以下三个组件:

  • Service Interface(服务接口):一个或多个接口,其具体实现类通过 SPI 机制被动态加载;
  • Service Provider Interface(服务提供者接口):标准接口,揭示具体服务实现的方法;
  • Service Provider(服务提供者):服务提供者实现,必须实现服务提供者接口。

使用 Java SPI 机制大致的流程如下:

  1. 定义服务接口;
  2. 编写服务实现类,并实现服务提供者接口;
  3. 将服务实现类注册到 META-INF/services/{serviceInterface} 的配置文件中;
  4. 使用 java.util.ServiceLoader 加载本地注册的服务实现。

具体示例代码如下:

定义服务接口

public interface HelloService {
    void sayHello(String name);
}

定义服务实现类

public class HelloServiceImpl implements HelloService {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello " + name);
    }
}

定义服务提供者接口

public interface HelloServiceProvider {
    HelloService getHelloService();
}

实现服务提供者接口

public class DefaultHelloServiceProvider implements HelloServiceProvider {
    @Override
    public HelloService getHelloService() {
        return new HelloServiceImpl();
    }
}

注册服务实现类

在项目的 src/main/resources/META-INF/services/ 目录下创建 com.example.spi.HelloServiceProvider 文件,内容如下:

com.example.spi.DefaultHelloServiceProvider

加载服务实现类

public class App {
    public static void main(String[] args) {
        ServiceLoader<HelloServiceProvider> loaders = ServiceLoader.load(HelloServiceProvider.class);
        for (HelloServiceProvider loader : loaders) {
            HelloService service = loader.getHelloService();
            service.sayHello("world");
        }
    }
}

Java SPI 源码浅析

Java SPI 机制是基于服务接口的类加载机制实现的。在 Java 中,一个类的加载过程主要分为三个阶段:加载,链接和初始化。

在加载阶段,类加载器通过获取目标字节码并生成 Class 对象。链接阶段主要包括如下三个步骤:

  • 校验:确保 Class 文件的字节流符合 Java 虚拟机规范;
  • 准备:为静态成员变量分配内存并设置默认值;
  • 解析:将符号引用替换为直接引用。

初始化阶段即为执行静态代码块,并初始化静态成员变量。Java SPI 在加载服务实现类时,主要使用到类加载机制的链接机制,具体来说,就是在 SPI 机制中,java.util.ServiceLoader 加载到具体的服务实现类时,通过 Class.forName() 方法加载类,并执行类加载器的链接操作。

同样以 java.util.ServiceLoader 为例,其源码如下:

public final class ServiceLoader<S> implements Iterable<S> {
    private final Class<S> service;

    ...

    private ServiceLoader(Class<S> svc) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if (cl == null) {
            cl = ClassLoader.getSystemClassLoader();
        }
        acc = SecurityManager? acc = SecurityManager.getStackAccessControlContext(): null;
        this.loader = new LazyIterator(service, cl);
    }

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
        return new ServiceLoader<>(service, Objects.requireNonNull(loader, "ClassLoader cannot be null"));
    }

}

其中,Class.forName() 方法的具体实现代码如下:

public static Class<?> forName(String className, boolean initialize,
                               ClassLoader loader)
        throws ClassNotFoundException {
    Class<?> clazz;
    if (!checkName(className))
        throw new ClassNotFoundException(className);
    try {
        if (loader == null) {
            clazz = Class.forName(className, initialize, ClassLoader.getSystemClassLoader());
        } else {
            clazz = loader.loadClass(className);
        }
    } catch (ClassNotFoundException e) {
        //如果指定的类还没加载,并且有父类加载器,再去父类加载器中查找
        Class<?> c = null;
        if (parent != null) {
            c = parent.loadClass(name, false);
        } else {
            c = findBootstrapClassOrNull(name);
        }
        if (c != null) {
            //如果在父类加载器中找到此类,就使用父类加载器的定义
            clazz = c;
        } else {
            //否则,抛出找不到类的异常
            throw new ClassNotFoundException(name);
        }
    }
    return clazz;
}

从源码可以看出,Java SPI 的实现主要依靠 Java 的类加载机制和反射机制。其中,java.util.ServiceLoader 类的 LazyIterator 内部类通过迭代器模式动态加载服务实现类,其中涉及到类加载、反射等技术。

结语

本文详细讲解了 Java SPI 机制的原理及使用方法,并通过具体的示例代码说明了其具体实现。同时,还分析了 Java SPI 的源码,帮助读者更好的了解 SPI 机制的内部机制,为读者进一步深入学习提供了帮助。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解java实践SPI机制及浅析源码 - Python技术站

(0)
上一篇 2023年5月22日
下一篇 2023年5月22日

相关文章

  • Laravel框架 redis hget() scontains()方法报错解决 阿星小栈

      问题代码: if(!Redis::scontains($redisKey, $path)){ return response()->json([ ‘code’ => 99998, ‘message’ => ‘无操作权限’ ], 200); } if(!Redis::hget($redisKey, ‘admin_id’)){ return…

    Redis 2023年4月13日
    00
  • Python SQLAlchemy库的使用方法

    下面是Python SQLAlchemy库的使用方法的完整攻略。 什么是SQLAlchemy SQLAlchemy是一个用于Python的SQL工具包和ORM框架。它为Python程序员提供了丰富和强大的SQL编程体验,同时支持Python 3和SQL 2003。 安装SQLAlchemy 安装SQLAlchemy可以使用pip命令,具体如下: pip in…

    database 2023年5月21日
    00
  • mysql获得60天前unix时间思路及代码

    获取60天前的UNIX时间的主要思路是使用MySQL函数来进行日期计算。具体步骤如下: 使用MySQL函数NOW()获取当前时间,这个函数返回当前系统时间的日期和时间部分。 使用MySQL函数UNIX_TIMESTAMP()将当前时间转换为UNIX时间戳,这个函数返回从1970年1月1日午夜(格林威治标准时间)到当前时间的秒数。 使用MySQL函数INTER…

    database 2023年5月22日
    00
  • Redis缓存实例分步详解

    Redis缓存实例分步详解 Redis是一个高性能的非关系型数据库,也是一个常用的缓存技术。本文将详细讲解如何使用Redis实现缓存,包括以下步骤: 安装Redis 配置Redis 使用Redis实现缓存 1. 安装Redis Redis的安装有多种方式,可以通过源码编译、apt-get安装、docker安装等等。这里以通过apt-get方式安装为例。具体步…

    database 2023年5月22日
    00
  • oracle冷备份恢复和oracle异机恢复使用方法

    Oracle冷备份恢复使用方法 什么是Oracle冷备份? Oracle冷备份指的是在关闭数据库后进行的备份。在备份期间,不会进行任何数据库操作,保证备份的正确性和一致性。 Oracle冷备份包括数据文件,控制文件和归档日志文件。 Oracle冷备份的步骤 停止Oracle数据库服务。 $ sqlplus / as sysdba SQL> shutdo…

    database 2023年5月22日
    00
  • 通过Shell脚本批量创建服务器上的MySQL数据库账号

    下面是通过Shell脚本批量创建服务器上的MySQL数据库账号的完整攻略。 一、前提条件 在执行Shell脚本批量创建MySQL数据库账号之前,需要满足以下前提条件: 在服务器上安装MySQL数据库,并拥有root用户权限; 已经安装并配置好MySQL客户端程序(mysql和mysqladmin); 已经创建好目标数据库,并准备好数据库授权方式和授权对象。 …

    database 2023年5月22日
    00
  • Java如何读取配置文件并赋值静态变量

    Java读取配置文件并将值赋给静态变量的操作是应用程序中常见的需求。下面是以properties文件为例,讲解如何读取并赋值静态变量的步骤: 1. 准备配置文件 首先需要准备一个.properties文件,其中包含一组键值对,如下所示: jdbc.url=jdbc:mysql://localhost:3306/test jdbc.username=root …

    database 2023年5月21日
    00
  • MongoDB使用profile分析慢查询的步骤

    下面是MongoDB使用profile分析慢查询的完整攻略: 1. 开启profile功能 在MongoDB中,可以通过开启profile功能来记录所有的操作信息,包括查询操作。使用profile功能需要在MongoDB启动时指定相应的配置,并在Mongo shell中设置。 如下是MongoDB启动时的配置: mongod –profile=2 # 记录…

    database 2023年5月21日
    00
合作推广
合作推广
分享本页
返回顶部