From 5f5f0eaab0de91b0f094408f52ef4d3e7db424f4 Mon Sep 17 00:00:00 2001 From: Mr-Summers <56919586+Mr-Summers@users.noreply.github.com> Date: Wed, 22 Jan 2020 16:31:05 +0800 Subject: [PATCH] =?UTF-8?q?Delete=20Java=20SPI=E6=9C=BA=E5=88=B6=E8=AF=A6?= =?UTF-8?q?=E8=A7=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...72\345\210\266\350\257\246\350\247\243.md" | 309 ------------------ 1 file changed, 309 deletions(-) delete mode 100644 "database/note/Java SPI\346\234\272\345\210\266\350\257\246\350\247\243.md" diff --git "a/database/note/Java SPI\346\234\272\345\210\266\350\257\246\350\247\243.md" "b/database/note/Java SPI\346\234\272\345\210\266\350\257\246\350\247\243.md" deleted file mode 100644 index 1bc7becf..00000000 --- "a/database/note/Java SPI\346\234\272\345\210\266\350\257\246\350\247\243.md" +++ /dev/null @@ -1,309 +0,0 @@ -# Java SPI机制详解 - -### 1、什么是SPI? - -``` - SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制, 比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。 -``` - -![](C:\Users\63198\Desktop\SPI.jpg) - -​ 如上图所示,接口对应的抽象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实现 - -```java -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实现 - -```java - /** - * 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`: - -```java -private static void loadInitialDrivers() { - String drivers; - try { - drivers = AccessController.doPrivileged(new PrivilegedAction() { - 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() { - public Void run() { - - ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class); - Iterator 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机制查找驱动 - -```java -public final class ServiceLoader - implements Iterable -{ - - private static final String PREFIX = "META-INF/services/"; -``` - -```java -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代码块 - -```java -/** - * 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); - - } -``` - -将自己注册到驱动管理器的驱动列表中 - -```java -public class DriverManager { - - - // List of registered JDBC drivers - private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>(); - -``` - -当获取连接的时候调用驱动管理器的连接方法从列表中获取。 - -```java - @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())); - } -``` - -```java - 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"); - } -``` - -```java -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; - } - -``` -