探索 Java ServiceLoader、Dubbo ExtensionLoader:构建灵活可扩展的应用程序的利器(上)

简介: 探索 Java ServiceLoader、Dubbo ExtensionLoader:构建灵活可扩展的应用程序的利器

介绍

SPI 全称为 (Service Provider Interface) 服务提供接口,JDK 内置的一种服务提供发现机制

SPI 是一种动态替换发现的机制, 比如有个接口,想在运行时动态的给它添加实现,你只需要添加一个实现类

经常遇到的就是 java.sql.Driver 接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL、PostgreSQL 都有不同的实现提供给用户,而 Java 的 SPI 机制可以为某个接口寻找服务实现

如上图所示,接口对应的抽象 SPI 接口;实现方实现 SPI 接口;调用方依赖 SPI 接口,在概念上更依赖调用方;组织上位于调用方所在的包中,实现位于独立的包中

服务提供者提供了一种接口的实现之后,需要在 classpath 路径下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类全路径名称;当其他的应用程序需要这个服务的时候,就可以通过查找这个 jar 包(一般都是以 jar 包做依赖) META-INF/services/ 中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK 中查找服务实现的工具类是:java.util.ServiceLoader

接口所在的提供方定义一组规范,一般是由开发人员定义出来

服务提供者,基于接口规范实现、自定义自己的逻辑,便于其他应用程序进行 Call

调用者,基于全限定路径接口名,在服务提供方加载此接口的实现类,做应用程序中自身的特殊处理,此调用者可以是其他服务

总之,通过 SPI 机制,应用程序可以以插件化的方式扩展功能,而无需显式地依赖具体的实现类。这种松耦合的设计使得应用程序更加灵活、可扩展和可维护

Java SPI

数据库:DriverManager、Spring、Dubbo 等都使用到了 SPI 机制,这里以 JDBC DriverManager 为例,看一下其是如何实现的

DriverManager 是 JDBC 里管理、注册不同数据库 Driver 工具类,针对一个数据库,可能会存在多种不同的数据库驱动实现;当我们希望在使用特定的驱动实现时,不希望修改现有的代码,而是直接通过一个简单的配置就能达到效果。

当我们在运用 Class.forName("com.mysql.jdbc.Driver") 加载 MySQL驱动后,就会执行其中的静态代码将 Driver 注册到 DriverManager 中,以便后续的使用

在使用 MySQL 驱动时,会有一个疑问,DriverManager 是如何获取确定的驱动类的,下面来具体介绍它的实现过程。

Driver 实现类

驱动类的静态代码块中,调用 DriverManager#registerDriver 注册驱动方法,通过 new 实例化一个驱动类作为参数传递给驱动管理器

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
    /**
     * 构造一个新的实例并会将其注册到驱动管理器中
     * @throws SQLException if a database error occurs.
     */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

DriverManager 驱动管理器类

DriverManager 静态初始化代码块,如下:

// 通过检查 System 属性加载初始 JDBC 驱动程序,然后使用 ServiceLoader 机制
static {
   loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

loadInitialDrivers 方法

其内部的静态代码块中有一个 loadInitialDrivers 方法,其用到了上文提到的 SPI 工具类ServiceLoader,如下:

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;
   }
   // If the driver is packaged as a Service Provider, load it.
   // Get all the drivers through the classloader
   // exposed as a java.sql.Driver.class service.
   // ServiceLoader.load() replaces the sun.misc.Providers()
   AccessController.doPrivileged(new PrivilegedAction<Void>() {
       public Void run() {
       // 会实例化 LazyIterator 懒迭代器以及 Driver 具体的实现类对象
           ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
           Iterator<Driver> driversIterator = loadedDrivers.iterator();
           // 调用的是 ServiceLoader.LazyIterator 懒迭代器的 iterator 方法
           try{
               // 调用 ServiceLoader.LazyIterator#hasNextService 方法
               // 会拼接 META-INF/services/ 前缀
               while(driversIterator.hasNext()) {
                   // 调用 ServiceLoader.LazyIterator#nextService 方法
                   // 会调用 Class.forName 实例化具体的实现类对象
                   driversIterator.next();
               }
           } catch(Throwable t) {
           // Do nothing
           }
           return null;
       }
   });
   // 省略其他代码 ....... 
}

registerDriver 方法

通过以上静态方法执行完过后,当前服务内所有的驱动实现类都会被加载进来,然后继续分析 DriverManager#registerDriver 方法的调用,源码如下:

// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
public static synchronized void registerDriver(java.sql.Driver driver,
       DriverAction da)
   throws SQLException {
   /* 将当前驱动添加到集合中,在获取连接时会用到该集合 */
   if(driver != null) {
       registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
   } else {
       // This is for compatibility with the original DriverManager
       throw new NullPointerException();
   }
   println("registerDriver: " + driver);
}

getConnection 方法

通过数据库地址、用户、密码信息,获取数据库连接

@CallerSensitive
public static Connection getConnection(String url,
    String user, String password) throws SQLException {
    java.util.Properties info = new java.util.Properties();
    if (user != null) {
        info.put("user", user);
    }
    if (password != null) {
        info.put("password", password);
    }
    return (getConnection(url, info, Reflection.getCallerClass()));
}

getConnection 重载方法会遍历 registeredDrivers 集合,通过驱动实现类和基础信息来与数据库建立连接,开启会话与 MySQL 服务端进行通信,如下:

private static Connection getConnection(
   String url, java.util.Properties info, Class<?> caller) throws SQLException {
   // 获取类加载器
   // 判别数据库地址
   if(url == null) {
       throw new SQLException("The url cannot be null", "08001");
   }
   println("DriverManager.getConnection(\"" + url + "\")");
   // Walk through the loaded registeredDrivers attempting to make a connection.
   // Remember the first exception that gets raised so we can reraise it.
   SQLException reason = null;
   // 遍历数据库驱动集合
   for(DriverInfo aDriver : registeredDrivers) {
       // 判断调用方是否有加载驱动程序的权限
       if(isDriverAllowed(aDriver.driver, callerCL)) {
           try {
               println("    trying " + aDriver.driver.getClass().getName());
               // 通过驱动实现类来获取数据库连接
               Connection con = aDriver.driver.connect(url, info);
               if (con != null) {
                   // Success!
                   println("getConnection returning " + aDriver.driver.getClass().getName());
                   return (con);
               }
           } catch (SQLException ex) {
               if (reason == null) {
                   reason = ex;
               }
           }
       } else {
           println("    skipping: " + aDriver.getClass().getName());
       }
   }
   // 省略其他代码 ......
}

ServiceLoader 核心类

首先看一下 ServiceLoader 类结构

// 配置文件的路径
private static final String PREFIX = "META-INF/services/";
// 加载服务类或者接口
private final Class<S> service;
// 类加载器
private final ClassLoader loader;
// 访问权限的上下文对象
private final AccessControlContext acc;
// 保存已经加载的服务类
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 内部类,它是用来真正加载服务类
private LazyIterator lookupIterator;

核心 load 方法创建了一些属性,重要的是实例化了内部类 > LazyIterator

private ServiceLoader(Class<S> svc, ClassLoader cl) {
  // 要加载的接口
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}
public void reload() {
  // 先清空
    providers.clear();
    // 实例化内部类
    lookupIterator = new LazyIterator(service, loader);
}

查找要加载的接口实现类以及创建实现类过程,都在内部类 LazyIterator 中完成,当在 DriverManager 中调用 Iterator#hasNext、Iterator#next 方法时,实际上调用的都是 LazyIterator 相应的方法 hasNextService、nextService



相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
10月前
|
设计模式 消息中间件 传感器
Java 设计模式之观察者模式:构建松耦合的事件响应系统
观察者模式是Java中常用的行为型设计模式,用于构建松耦合的事件响应系统。当一个对象状态改变时,所有依赖它的观察者将自动收到通知并更新。该模式通过抽象耦合实现发布-订阅机制,广泛应用于GUI事件处理、消息通知、数据监控等场景,具有良好的可扩展性和维护性。
666 8
|
10月前
|
机器学习/深度学习 人工智能 自然语言处理
Java与生成式AI:构建内容生成与创意辅助系统
生成式AI正在重塑内容创作、软件开发和创意设计的方式。本文深入探讨如何在Java生态中构建支持文本、图像、代码等多种生成任务的创意辅助系统。我们将完整展示集成大型生成模型(如GPT、Stable Diffusion)、处理生成任务队列、优化生成结果以及构建企业级生成式AI应用的全流程,为Java开发者提供构建下一代创意辅助系统的完整技术方案。
449 10
|
10月前
|
人工智能 算法 Java
Java与AI驱动区块链:构建智能合约与去中心化AI应用
区块链技术和人工智能的融合正在开创去中心化智能应用的新纪元。本文深入探讨如何使用Java构建AI驱动的区块链应用,涵盖智能合约开发、去中心化AI模型训练与推理、数据隐私保护以及通证经济激励等核心主题。我们将完整展示从区块链基础集成、智能合约编写、AI模型上链到去中心化应用(DApp)开发的全流程,为构建下一代可信、透明的智能去中心化系统提供完整技术方案。
562 3
|
10月前
|
机器学习/深度学习 人工智能 监控
Java与AI模型部署:构建企业级模型服务与生命周期管理平台
随着企业AI模型数量的快速增长,模型部署与生命周期管理成为确保AI应用稳定运行的关键。本文深入探讨如何使用Java生态构建一个企业级的模型服务平台,实现模型的版本控制、A/B测试、灰度发布、监控与回滚。通过集成Spring Boot、Kubernetes、MLflow和监控工具,我们将展示如何构建一个高可用、可扩展的模型服务架构,为大规模AI应用提供坚实的运维基础。
689 0
|
10月前
|
人工智能 Java 物联网
Java与边缘AI:构建离线智能的物联网与移动应用
随着边缘计算和终端设备算力的飞速发展,AI推理正从云端向边缘端迁移。本文深入探讨如何在资源受限的边缘设备上使用Java构建离线智能应用,涵盖从模型优化、推理加速到资源管理的全流程。我们将完整展示在Android设备、嵌入式系统和IoT网关中部署轻量级AI模型的技术方案,为构建真正实时、隐私安全的边缘智能应用提供完整实践指南。
644 3
|
10月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
815 8
|
10月前
|
人工智能 监控 Java
Java与AI智能体:构建自主决策与工具调用的智能系统
随着AI智能体技术的快速发展,构建能够自主理解任务、制定计划并执行复杂操作的智能系统已成为新的技术前沿。本文深入探讨如何在Java生态中构建具备工具调用、记忆管理和自主决策能力的AI智能体系统。我们将完整展示从智能体架构设计、工具生态系统、记忆机制到多智能体协作的全流程,为Java开发者提供构建下一代自主智能系统的完整技术方案。
1128 4
|
10月前
|
安全 Java
Java异常处理:程序世界的“交通规则
Java异常处理:程序世界的“交通规则
415 98
|
10月前
|
机器学习/深度学习 分布式计算 Java
Java与图神经网络:构建企业级知识图谱与智能推理系统
图神经网络(GNN)作为处理非欧几里得数据的前沿技术,正成为企业知识管理和智能推理的核心引擎。本文深入探讨如何在Java生态中构建基于GNN的知识图谱系统,涵盖从图数据建模、GNN模型集成、分布式图计算到实时推理的全流程。通过具体的代码实现和架构设计,展示如何将先进的图神经网络技术融入传统Java企业应用,为构建下一代智能决策系统提供完整解决方案。
766 0