源碼級深度理解 Java SPI( 四 )


  • MySQL:在 MySQL的 Java 驅動包 mysql-connector-java-XXX.jar 中,可以找到 META-INF/services 目錄,該目錄下會有一個名字為java.sql.Driver 的文件,文件內容是com.mysql.cj.jdbc.Driver 。
com.mysql.cj.jdbc.Driver 正是 MySQL 版的 java.sql.Driver 實現 。如下圖所示:
源碼級深度理解 Java SPI

文章插圖
  • PostgreSQL 實現:在 PostgreSQL 的 Java 驅動包 postgresql-42.0.0.jar 中,也可以找到同樣的配置文件,文件內容是 org.postgresql.Driver , org.postgresql.Driver 正是 PostgreSQL 版的 java.sql.Driver 實現 。
(3)創建數據庫連接
以 MySQL 為例,創建數據庫連接代碼如下:
final String DB_URL = String.format("jdbc:mysql://%s:%s/%s", DB_HOST, DB_PORT, DB_SCHEMA);connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);4.1.2 DriverManager從前文 , 我們已經知道 DriverManager 是創建數據庫連接的關鍵 。它究竟是如何工作的呢?
可以看到是加載實例化驅動的,接著看 loadInitialDrivers 方法:
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;}// 通過 classloader 獲取所有實現 java.sql.Driver 的驅動類AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {// 利用 SPI,記載所有 Driver 服務ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);// 獲取迭代器Iterator<Driver> driversIterator = loadedDrivers.iterator();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);}}}上面的代碼主要步驟是:
  1. 從系統變量中獲取驅動的實現類 。
  2. 利用 SPI 來獲取所有驅動的實現類 。
  3. 遍歷所有驅動 , 嘗試實例化各個實現類 。
  4. 根據第 1 步獲取到的驅動列表來實例化具體的實現類 。
需要關注的是下面這行代碼:
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);這里實際獲取的是java.util.ServiceLoader.LazyIterator 迭代器 。調用其 hasNext 方法時,會搜索 classpath 下以及 jar 包中的 META-INF/services 目錄,查找 java.sql.Driver 文件 , 并找到文件中的驅動實現類的全限定名 。調用其 next 方法時 , 會根據驅動類的全限定名去嘗試實例化一個驅動類的對象 。
4.2 SPI 應用案例之 Common-Loggincommon-logging(也稱 Jakarta Commons Logging,縮寫 JCL)是常用的日志門面工具包 。common-logging 的核心類是入口是 LogFactory , LogFatory 是一個抽象類,它負責加載具體的日志實現 。
其入口方法是 LogFactory.getLog 方法,源碼如下:
public static Log getLog(Class clazz) throws LogConfigurationException {return getFactory().getInstance(clazz);}public static Log getLog(String name) throws LogConfigurationException {return getFactory().getInstance(name);}從以上源碼可知 , getLog 采用了工廠設計模式 , 是先調用 getFactory 方法獲取具體日志庫的工廠類,然后根據類名稱或類型創建日志實例 。
LogFatory.getFactory 方法負責選出匹配的日志工廠,其源碼如下:
public static LogFactory getFactory() throws LogConfigurationException {// 省略...// 加載 commons-logging.properties 配置文件Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);// 省略...// 決定創建哪個 LogFactory 實例// (1)嘗試讀取全局屬性 org.apache.commons.logging.LogFactoryif (isDiagnosticsEnabled()) {logDiagnostic("[LOOKUP] Looking for system property [" + FACTORY_PROPERTY +"] to define the LogFactory subclass to use...");}try {// 如果指定了 org.apache.commons.logging.LogFactory 屬性,嘗試實例化具體實現類String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);if (factoryClass != null) {if (isDiagnosticsEnabled()) {logDiagnostic("[LOOKUP] Creating an instance of LogFactory class '" + factoryClass +"' as specified by system property " + FACTORY_PROPERTY);}factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);} else {if (isDiagnosticsEnabled()) {logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined.");}}} catch (SecurityException e) {// 異常處理} catch (RuntimeException e) {// 異常處理}// (2)利用 Java SPI 機制 , 嘗試在 classpatch 的 META-INF/services 目錄下尋找 org.apache.commons.logging.LogFactory 實現類if (factory == null) {if (isDiagnosticsEnabled()) {logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID +"] to define the LogFactory subclass to use...");}try {final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);if( is != null ) {// This code is needed by EBCDIC and other strange systems.// It's a fix for bugs reported in xercesBufferedReader rd;try {rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));} catch (java.io.UnsupportedEncodingException e) {rd = new BufferedReader(new InputStreamReader(is));}String factoryClassName = rd.readLine();rd.close();if (factoryClassName != null && ! "".equals(factoryClassName)) {if (isDiagnosticsEnabled()) {logDiagnostic("[LOOKUP]Creating an instance of LogFactory class " +factoryClassName +" as specified by file '" + SERVICE_ID +"' which was present in the path of the context classloader.");}factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader );}} else {// is == nullif (isDiagnosticsEnabled()) {logDiagnostic("[LOOKUP] No resource file with name '" + SERVICE_ID + "' found.");}}} catch (Exception ex) {// note: if the specified LogFactory class wasn't compatible with LogFactory// for some reason, a ClassCastException will be caught here, and attempts will// continue to find a compatible class.if (isDiagnosticsEnabled()) {logDiagnostic("[LOOKUP] A security exception occurred while trying to create an" +" instance of the custom factory class" +": [" + trim(ex.getMessage()) +"]. Trying alternative implementations...");}// ignore}}// (3)嘗試從 classpath 目錄下的 commons-logging.properties 文件中查找 org.apache.commons.logging.LogFactory 屬性if (factory == null) {if (props != null) {if (isDiagnosticsEnabled()) {logDiagnostic("[LOOKUP] Looking in properties file for entry with key '" + FACTORY_PROPERTY +"' to define the LogFactory subclass to use...");}String factoryClass = props.getProperty(FACTORY_PROPERTY);if (factoryClass != null) {if (isDiagnosticsEnabled()) {logDiagnostic("[LOOKUP] Properties file specifies LogFactory subclass '" + factoryClass + "'");}factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);// TODO: think about whether we need to handle exceptions from newFactory} else {if (isDiagnosticsEnabled()) {logDiagnostic("[LOOKUP] Properties file has no entry specifying LogFactory subclass.");}}} else {if (isDiagnosticsEnabled()) {logDiagnostic("[LOOKUP] No properties file available to determine" + " LogFactory subclass from..");}}}// (4)以上情況都不滿足 , 實例化默認實現類 org.apache.commons.logging.impl.LogFactoryImplif (factory == null) {if (isDiagnosticsEnabled()) {logDiagnostic("[LOOKUP] Loading the default LogFactory implementation '" + FACTORY_DEFAULT +"' via the same classloader that loaded this LogFactory" +" class (ie not looking in the context classloader).");}factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);}if (factory != null) {/*** Always cache using context class loader.*/cacheFactory(contextClassLoader, factory);if (props != null) {Enumeration names = props.propertyNames();while (names.hasMoreElements()) {String name = (String) names.nextElement();String value = https://www.huyubaike.com/biancheng/props.getProperty(name);factory.setAttribute(name, value);}}}return factory;}

推薦閱讀