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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 探索 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
目录
相关文章
|
14天前
|
人工智能 Java API
Java也能快速搭建AI应用?一文带你玩转Spring AI可落地性
Java语言凭借其成熟的生态与解决方案,特别是通过 Spring AI 框架,正迅速成为 AI 应用开发的新选择。本文将探讨如何利用 Spring AI Alibaba 构建在线聊天 AI 应用,并实现对其性能的全面可观测性。
|
1月前
|
存储 缓存 Java
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
95 3
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
|
9天前
|
人工智能 Java API
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
|
11天前
|
缓存 Java 物联网
CRaC技术助力ACS上的Java应用启动加速
容器计算服务借助ACS的柔性算力特性并搭配CRaC技术极致地提升Java类应用的启动速度。
|
11天前
|
人工智能 Java API
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
|
2月前
|
Java 编译器 开发者
Java中的this关键字详解:深入理解与应用
本文深入解析了Java中`this`关键字的多种用法
203 9
|
2月前
|
Java 应用服务中间件 API
【潜意识Java】javaee中的SpringBoot在Java 开发中的应用与详细分析
本文介绍了 Spring Boot 的核心概念和使用场景,并通过一个实战项目演示了如何构建一个简单的 RESTful API。
50 5
|
2月前
|
人工智能 自然语言处理 搜索推荐
【潜意识Java】了解并详细分析Java与AIGC的结合应用和使用方式
本文介绍了如何将Java与AIGC(人工智能生成内容)技术结合,实现智能文本生成。
189 5
|
2月前
|
SQL Java 数据库连接
【潜意识Java】深入理解MyBatis,从基础到高级的深度细节应用
本文详细介绍了MyBatis,一个轻量级的Java持久化框架。内容涵盖MyBatis的基本概念、配置与环境搭建、基础操作(如创建实体类、Mapper接口及映射文件)以及CRUD操作的实现。此外,还深入探讨了高级特性,包括动态SQL和缓存机制。通过代码示例,帮助开发者更好地掌握MyBatis的使用技巧,提升数据库操作效率。总结部分强调了MyBatis的优势及其在实际开发中的应用价值。
40 1
|
3月前
|
安全 算法 Java
Java CAS原理和应用场景大揭秘:你掌握了吗?
CAS(Compare and Swap)是一种乐观锁机制,通过硬件指令实现原子操作,确保多线程环境下对共享变量的安全访问。它避免了传统互斥锁的性能开销和线程阻塞问题。CAS操作包含三个步骤:获取期望值、比较当前值与期望值是否相等、若相等则更新为新值。CAS广泛应用于高并发场景,如数据库事务、分布式锁、无锁数据结构等,但需注意ABA问题。Java中常用`java.util.concurrent.atomic`包下的类支持CAS操作。
104 2

热门文章

最新文章