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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 探索 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



相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1月前
|
存储 监控 安全
单位网络监控软件:Java 技术驱动的高效网络监管体系构建
在数字化办公时代,构建基于Java技术的单位网络监控软件至关重要。该软件能精准监管单位网络活动,保障信息安全,提升工作效率。通过网络流量监测、访问控制及连接状态监控等模块,实现高效网络监管,确保网络稳定、安全、高效运行。
62 11
|
9天前
|
监控 Dubbo Java
Java Dubbo 面试题
Java Dubbo相关基础面试题
|
2月前
|
XML Java 测试技术
从零开始学 Maven:简化 Java 项目的构建与管理
Maven 是一个由 Apache 软件基金会开发的项目管理和构建自动化工具。它主要用在 Java 项目中,但也可以用于其他类型的项目。
77 1
从零开始学 Maven:简化 Java 项目的构建与管理
|
2月前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
269 12
基于开源框架Spring AI Alibaba快速构建Java应用
|
2月前
|
SQL 安全 Java
Java 异常处理:筑牢程序稳定性的 “安全网”
本文深入探讨Java异常处理,涵盖异常的基础分类、处理机制及最佳实践。从`Error`与`Exception`的区分,到`try-catch-finally`和`throws`的运用,再到自定义异常的设计,全面解析如何有效管理程序中的异常情况,提升代码的健壮性和可维护性。通过实例代码,帮助开发者掌握异常处理技巧,确保程序稳定运行。
58 1
|
2月前
|
Java Android开发
Eclipse Java 构建路径
Eclipse Java 构建路径
47 3
|
2月前
|
IDE Java 编译器
开发 Java 程序一定要安装 JDK 吗
开发Java程序通常需要安装JDK(Java Development Kit),因为它包含了编译、运行和调试Java程序所需的各种工具和环境。不过,某些集成开发环境(IDE)可能内置了JDK,或可使用在线Java编辑器,无需单独安装。
103 1
|
2月前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
75 2
|
SQL 缓存 Dubbo
探索 Java ServiceLoader、Dubbo ExtensionLoader:构建灵活可扩展的应用程序的利器(下)
探索 Java ServiceLoader、Dubbo ExtensionLoader:构建灵活可扩展的应用程序的利器(下)
145 0