介绍
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