轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

      • Java8--Lambda 表达式、Stream 和时间 API
      • Java集合
      • Java IO
      • Java 文件操作
      • Java 网络编程
      • Java运行期动态能力
      • Java可插入注解处理器
      • Java基准测试(JMH)
      • Java性能分析(Profiler)
      • Java调试(JDI与JDWP)
      • Java管理与监控(JMX)
      • Java加密体系(JCA)
      • Java服务发现(SPI)
        • Java SPI的详细介绍
          • 1. SPI的工作原理
          • 2. Java SPI的API解析
          • 3. SPI的常见使用场景
        • Java SPI实践
          • 1. 如何创建SPI服务
          • 1.1 创建服务接口
          • 1.2 创建服务提供者
          • 1.3 创建服务配置文件
          • 2. 如何使用SPI服务
          • 3. 实际案例分析
          • 3. 实际案例分析
          • 3.1 JDBC
          • 3.2 Logback
          • 3.3 Dubbo
        • Java SPI的优点和局限性
          • 1. 优点
          • 2. 局限性
        • 五、Java SPI与其他服务发现技术的比较
          • 1. 与OSGi的比较
          • 2. 与Spring的FactoryBeans的比较
        • 总结
      • Java随机数生成研究
      • Java数据库连接(JDBC)
      • Java历代版本新特性
      • 写好Java Doc
      • 聊聊classpath及其资源获取
    • 并发

    • 经验

    • JVM

    • 企业应用

  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • 核心
轩辕李
2023-05-09
目录

Java服务发现(SPI)

Java SPI(Service Provider Interface)是Java中的一种服务发现机制。

它允许第三方提供者实现特定的接口,并使其实现在运行时可用。

# Java SPI的详细介绍

# 1. SPI的工作原理

Java SPI的工作原理是通过在META-INF/services目录下创建以接口全名命名的文件。文件的内容就是该服务接口的具体实现类。

当外部程序设备使用这个服务时,就能通过这个配置文件加载所有的实现类,然后通过Iterator来遍历并获取服务。

# 2. Java SPI的API解析

Java SPI的核心是ServiceLoader类。ServiceLoader.load(Class<S> service)可以加载指定服务的所有实现。

# 3. SPI的常见使用场景

SPI在许多Java库和框架中有广泛的应用,例如JDBC驱动程序加载、日志框架的选择等。

# Java SPI实践

# 1. 如何创建SPI服务

# 1.1 创建服务接口

首先,我们需要定义一个服务接口。例如:

public interface MessageService {
    String getMessage();
}

# 1.2 创建服务提供者

然后,我们可以创建一个或多个服务接口的实现。例如:

public class HelloWorldMessageService implements MessageService {
    @Override
    public String getMessage() {
        return "Hello World!";
    }
}

# 1.3 创建服务配置文件

在META-INF/services目录下创建一个以接口全名命名的文件,文件内容为接口的实现类全名。

例如,文件名为:com.example.MessageService

文件的内容为:

com.example.HelloWorldMessageService

# 2. 如何使用SPI服务

使用ServiceLoader来加载并使用服务:

ServiceLoader<MessageService> services = ServiceLoader.load(MessageService.class);
for (MessageService service : services) {
    System.out.println(service.getMessage());
}

# 3. 实际案例分析

当然可以,以下是关于JDBC、Logback、Dubbo的SPI实践案例分析。

# 3. 实际案例分析

# 3.1 JDBC

Java数据库连接(JDBC)是Java中用于连接数据库的一种标准方式。在JDBC中,数据库驱动程序被设计为SPI。当你尝试通过JDBC连接数据库时,JDBC API会通过SPI机制自动加载合适的驱动程序。这是通过在每个JDBC驱动JAR文件的META-INF/services目录下包含一个名为java.sql.Driver的文件来实现的,这个文件包含了该驱动的全类名。

在JDBC中,java.sql.DriverManager负责加载所有的JDBC驱动。源码中,我们可以看到这个类在初始化时使用了ServiceLoader来加载java.sql.Driver服务:

private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {

            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();

            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            // Do nothing
            }
            return null;
        }
    });

    println("JDBC DriverManager initialized");
}

在上面的代码中,ServiceLoader.load(Driver.class)加载了所有的Driver实现,然后通过迭代器访问每个加载的驱动,确保它们被正确初始化。

# 3.2 Logback

Logback是一个Java日志框架,它使用了SPI机制来加载和配置日志上下文(LoggerContext)。具体来说,Logback定义了一个名为com.qos.logback.classic.spi.Configurator的SPI,任何实现了这个接口的类都会在Logback初始化时被加载和执行。这使得用户可以通过SPI机制提供自定义的日志配置。

在Logback中,ch.qos.logback.classic.util.ContextInitializer负责加载所有的Configurator实现:

private void autoConfig() throws JoranException {
    ServiceLoader<Configurator> configuratorServiceLoader = ServiceLoader.load(Configurator.class);
    Iterator<Configurator> iterator = configuratorServiceLoader.iterator();

    if (iterator.hasNext()) {
        Configurator configurator = iterator.next();
        configurator.setContext(context);
        configurator.configure(context);
    } else {
        defaultConfig();
    }
}

在上面的代码中,ServiceLoader.load(Configurator.class)加载了所有的Configurator实现,然后如果有可用的实现,就使用它来配置日志上下文。如果没有可用的实现,就使用默认的配置。

# 3.3 Dubbo

Dubbo是一个高性能的Java RPC框架。在Dubbo中,SPI被用于加载各种插件和扩展。

例如,Dubbo的协议、序列化方式、负载均衡策略等都是通过SPI机制来加载的。

Dubbo实际上并没有使用Java的标准Service Provider Interface (SPI),而是实现了自己的一套SPI机制。

Dubbo的SPI机制和Java的标准SPI在某些方面有相似之处,比如都是通过配置文件来指定接口的实现类,然后在运行时动态加载这些实现类。然而,Dubbo的SPI机制提供了更多高级的特性,比如支持对扩展点的自动装配,支持多个扩展点,等等。

在Dubbo中,扩展点的加载是由org.apache.dubbo.common.extension.ExtensionLoader类来完成的。这个类的设计采用了单例模式,ExtensionLoader在加载扩展点时,会首先从缓存中查找,如果没有找到,才会去加载配置文件并创建扩展点的实例。

private T loadExtension(String name) {
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<>());
        holder = cachedInstances.get(name);
    }
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

在上面的代码中,loadExtension方法用于加载指定名字的扩展。如果扩展已经被加载,就直接返回。否则,就创建一个新的扩展实例。

Dubbo的SPI机制支持多个扩展点,可以通过配置文件来指定默认的扩展。例如,对于序列化扩展,Dubbo定义了一个Serialization接口,并提供了多个实现,如Hessian2Serialization,JsonSerialization等。用户可以在dubbo.properties文件中设置默认的序列化方式:

dubbo.serialization=default

# Java SPI的优点和局限性

# 1. 优点

  • 简单易用:Java SPI的API非常简单,只需要遵循一定的规则就可以加载服务。
  • 强大的解耦能力:使用SPI可以将接口和实现完全分离,极大地提高了模块间的解耦性。

# 2. 局限性

  • SPI会加载所有的实现类:这可能会导致资源的浪费,特别是在有大量实现类且只需要使用其中一个实现类的情况下。
  • 延迟加载问题:Java SPI不支持延迟加载,即加载第一个符合要求的服务后就返回,而不是全部加载。

# 五、Java SPI与其他服务发现技术的比较

# 1. 与OSGi的比较

OSGi是一个更加强大的服务发现框架,它支持服务的动态加载和卸载,而Java SPI则不支持。

# 2. 与Spring的FactoryBeans的比较

Spring的FactoryBeans提供了更加灵活的服务加载方式,它支持按需加载和自动装配,而Java SPI则需要手动管理服务的加载。

# 总结

服务发现是构建可扩展和灵活的系统的关键。

Java SPI是一个简单但功能强大的服务发现机制,虽然存在一些局限性,但其简单易用的特性使得它在Java世界中得到了广泛的应用。

对于需要使用服务发现的开发者来说,理解和掌握Java SPI是非常有益的。

祝你变得更强!

编辑 (opens new window)
#SPI
上次更新: 2023/06/14
Java加密体系(JCA)
Java随机数生成研究

← Java加密体系(JCA) Java随机数生成研究→

最近更新
01
Spring Boot版本新特性
09-15
02
Spring框架版本新特性
09-01
03
Spring Boot开发初体验
08-15
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式