Java SPI机制详解

1、什么是SPI?

   SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制, 比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。

​ 如上图所示,接口对应的抽象SPI接口;实现方实现SPI接口;调用方依赖SPI接口。

​ SPI接口的定义在调用方,在概念上更依赖调用方;组织上位于调用方所在的包中,实现位于独立的包中。

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

2、SPI的用途

​ 数据库DriverManager、Spring、ConfigurableBeanFactory等都用到了SPI机制,这里以数据库DriverManager为例,看一下其实现的内幕。

​ DriverManager是jdbc里管理和注册不同数据库driver的工具类。针对一个数据库,可能会存在着不同的数据库驱动实现。我们在使用特定的驱动实现时,不希望修改现有的代码,而希望通过一个简单的配置就可以达到效果。 在使用mysql驱动的时候,会有一个疑问,DriverManager是怎么获得某确定驱动类的?我们在运用Class.forName("com.mysql.jdbc.Driver")加载mysql驱动后,就会执行其中的静态代码把driver注册到DriverManager中,以便后续的使用。

Driver实现

package com.mysql.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException; public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
} static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}

驱动的类的静态代码块中,调用DriverManager的注册驱动方法new一个自己当参数传给驱动管理器。

Mysql DriverManager实现

    /**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
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() { ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator(); /* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers); if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}

先查找jdbc.drivers属性的值,然后通过SPI机制查找驱动

public final class ServiceLoader<S>
implements Iterable<S>
{ private static final String PREFIX = "META-INF/services/";
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}

可以看到加载META-INF/services/ 文件夹下类名为文件名(这里相当于Driver.class.getName())的资源,然后将其加载到虚拟机。

注释有这么一句“Load these drivers, so that they can be instantiated.” 意思是加载SPI扫描到的驱动来触发他们的初始化。即触发他们的static代码块

/**
* Registers the given driver with the {@code DriverManager}.
* A newly-loaded driver class should call
* the method {@code registerDriver} to make itself
* known to the {@code DriverManager}. If the driver is currently
* registered, no action is taken.
*
* @param driver the new JDBC Driver that is to be registered with the
* {@code DriverManager}
* @param da the {@code DriverAction} implementation to be used when
* {@code DriverManager#deregisterDriver} is called
* @exception SQLException if a database access error occurs
* @exception NullPointerException if {@code driver} is null
* @since 1.8
*/
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException { /* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
} println("registerDriver: " + driver); }

将自己注册到驱动管理器的驱动列表中

public class DriverManager {

    // List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

当获取连接的时候调用驱动管理器的连接方法从列表中获取。

  @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()));
}
 private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
} 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 the caller does not have permission to load the driver then
// skip it.
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());
} } // if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
} println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
boolean result = false;
if(driver != null) {
Class<?> aClass = null;
try {
aClass = Class.forName(driver.getClass().getName(), true, classLoader);
} catch (Exception ex) {
result = false;
} result = ( aClass == driver.getClass() ) ? true : false;
} return result;
}

Java SPI机制详解的更多相关文章

  1. Java 反射机制详解(下)

    续:Java 反射机制详解(上) 三.怎么使用反射 想要使用反射机制,就必须要先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属 ...

  2. Java 反射机制详解(上)

    一.什么是反射 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象方法的功能称为java ...

  3. java异常处理机制详解

    java异常处理机制详解 程序很难做到完美,不免有各种各样的异常.比如程序本身有bug,比如程序打印时打印机没有纸了,比如内存不足.为了解决这些异常,我们需要知道异常发生的原因.对于一些常见的异常,我 ...

  4. Java反射机制详解

    Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反 ...

  5. Java GC机制详解

    垃圾收集 Garbage Collection 通常被称为“GC”,本文详细讲述Java垃圾回收机制. 导读: 1.什么是GC 2.GC常用算法 3.垃圾收集器 4.finalize()方法详解 5. ...

  6. java反射机制详解 及 Method.invoke解释

    JAVA反射机制 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法:这种动态获取的信息以及动态调用对象的方法的功能称为ja ...

  7. Java反射机制详解(3) -java的反射和代理实现IOC模式 模拟spring

    IOC(Inverse of Control) 可翻译为“控制反转”,但大多数人都习惯将它称为“依赖注入”.在Spring中,通过IOC可以将实现类.参数信息等配置在其对应的配置文件中,那么当 需要更 ...

  8. Java反射机制详解(1) -反射定义

    首先,我们在开始前提出一个问题: 1.在运行时,对于一个java类,能否知道属性和方法:能否去调用它的任意方法? 答案是肯定的. 本节所有目录如下: 什么是JAVA的反射机制 JDK中提供的Refle ...

  9. [转]Java反射机制详解

    目录 1反射机制是什么 2反射机制能做什么 3反射机制的相关API ·通过一个对象获得完整的包名和类名 ·实例化Class类对象 ·获取一个对象的父类与实现的接口 ·获取某个类中的全部构造函数 - 详 ...

随机推荐

  1. MySQL优化索引

    1.  MySQL如何使用索引 索引用于快速查找具有特定列值的行.如果没有索引,MySQL必须从第一行开始,然后遍历整个表以找到相关的行.表越大,花费越多.如果表中有相关列的索引,MySQL可以快速确 ...

  2. Python之matplotlib画图

    折线图: Matplotlib处理csv文件 这里写两个其他的. 柱状图,柱状图个人喜欢用pygal来画,有两篇文章 - Pygal之掷骰子 和 pygal之掷骰子 - 2颗面数为6的骰子.下面用ma ...

  3. 【程序包管理】篇章3:CentOS平台下软件包安装方法总结

    1.软件包安装前的学习:程序包管理的基础知识 2.程序包安装的方法介绍: rpm文件的二进制包安装:Linux程序包管理之rpm安装总结 yum安装:Linux程序包管理之yum源安装 链接:本地yu ...

  4. 平滑算法:三次样条插值(Cubic Spline Interpolation)

    https://blog.csdn.net/left_la/article/details/6347373 感谢强大的google翻译. 我从中认识到了航位推算dead reckoning,立方体样条 ...

  5. Plugin 插件体系

    Solon 的插件也可以叫扩展组件,相当于Spring 的 starter.Solon已经提供了大量的基础插件,但对第三方的框架适配目前较少. 插件 说明 boot插件:: 说明 org.noear: ...

  6. java连接mongodb数据库

    最近毕设需要用到这个数据库.然而又不会,没办法,只能上网学习学习. 记录一下java连接mongodb数据库的简单方法.这里只是记录一下学习.熟悉一下CURD方法. 但是毕业用到的是SpringBoo ...

  7. 聊聊ERP的VIP卡充值的那些事

    我们相信许多客户朋友,不管使用什么品牌的ERP系统,可能都有经历过各种各样的操作痛点,以及在某个阶段之前的功能无法满足现有的操作需求.今天我们就聊聊VIP卡充值操作遇到的一些问题以及相关解决方案,最大 ...

  8. 文本单词one-hot编码

    单词->字母->向量 神经网络是建立在数学的基础上进行计算的,因此对数字更敏感,不管是什么样的特征数据都需要以向量的形式喂入神经网络,无论是图片.文本.音频.视频都是一样. one-hot ...

  9. 数据库索引的基石----B树

    数据结构相对来说比较枯燥, 我尽量用最易懂的话,来把B树讲清楚.学过数据结构的人都接触过一个概念二叉树,简单来说,就是每个父节点最多有两个子节点.为了在二叉树上更快的进行元素的查找,人们通过不断的改进 ...

  10. 风炫安全WEB安全学习第二十二节课 DOM型XSS讲解

    风炫安全WEB安全学习第二十二节课 DOM型XSS讲解 Dom型XSS演示 通过Javascript,可以重构整个HTML文档,你可以添加.移除.改变或重排页面上的项目 要改变页面的某个东西,Java ...