1. 程式人生 > >【Java】ServiceLoader機制深入

【Java】ServiceLoader機制深入

ServiceLoader是在 jdk1.6開始引入的,它主要的功能是用來完成對SPI的provider的載入.

簡單理解其功能就是,根據給定的介面,找到當前介面所有實現的類.

現在給出一個這種機制的應用場景示例: jdbc的DriverManger,下面是一個程式碼片段

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

Connection conn = null;
...
try {
    conn =
       DriverManager.getConnection
("jdbc:mysql://localhost/test?" + "user=test&password=test"); // Do something with the Connection ... } catch (SQLException ex) { // handle any errors System.out.println("SQLException: " + ex.getMessage()); System.out.println("SQLState: " + ex.getSQLState
()); System.out.println("VendorError: " + ex.getErrorCode()); }

上面這段程式碼本身沒有很複雜,這裡我們需要了解的是DriverManager是如何正確的獲取到對應支援該url的Connection,下面是DriverManager的getConnection部分原始碼:

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()); } }

從上面的程式碼當中我們可以瞭解到DriverManager其實是通過不斷嘗試內部已經註冊的Driver來獲取對應的Connection,直到成功得到Connection為止,這部分還是比較好理解,這時候你會有新的疑問,
這個registeredDrivers是怎麼註冊的?(因為DriverManager本身是不知道當前應用環境有多少個實現,它是不可能通過new具體的例項來完成的)

對於registeredDrivers是完成它的初始化工作的,可以進一步看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");
}



    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();
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        println("DriverManager.initialize: jdbc.drivers = " + drivers);
        ...省略
    }

從上面的程式碼可以看到,首先它通過讀取系統引數來了解使用者指定的driver實現,繼而進一步通過ServiceLoader這個類完成其它的Driver的實現類.

現在的問題又進一步轉移到ServiceLoader是如何找到當前系統的Driver的實現類的.

通過ServiceLoader的原始碼我們瞭解到,ServiceLoader查詢對應的實現是通過一個統一規範的方式來查詢,如果實現了Driver這個介面,你就得按ServiceLoader指定的方式來暴露你的實現.

具體的暴露方式是:

  1. 在最終的jar檔案的META-INF/services/路徑下,配置當前jar實現介面的資訊檔案;
  2. 檔案的名稱就是實現介面的名稱,比如當前這個例子,這對應的檔名詞是java.sql.Driver;
  3. 檔案裡面的內容是具體的實現類完整的包資訊,比如:mysql的對應實現是com.mysql.cj.jdbc.Driver, 所以檔案裡面的內容也要和這個保持一致;

    另外需要注意的是,檔案裡面的內容可以是多行資訊,這種情況是你提供了多種實現.

下面我們去看一下mysql的driver檔案裡面配置資訊來核實一下我們的理解:
在這裡插入圖片描述
接下來,我們可以通過一個簡單的例子來感受一下:

測試專案使用maven來構建

1 定義抽象介面

專案結構如下截圖:
api-maven-package

下面是部分檔案的定義
1 Driver.java

package com.together.learning.java.spi;

/**
 * @author jiangjian
 */
public interface Driver {
    String getVendorName();
}

2 pom.xml檔案

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.together</groupId>
    <artifactId>driver-api</artifactId>
    <version>1.0-SNAPSHOT</version>
</project>

2 實現抽象介面

專案結構如下截圖:
api-impl

下面是部分檔案的定義
1 ZhangshanDriverImpl.java

package com.zhangsan.driver;

import com.together.learning.java.spi.Driver;
/**
 * @author jiangjian
 */
public class ZhangshanDriverImpl implements Driver {
    public String getVendorName() {
        return "zhangsan corp";
    }
}

2 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zhangsan</groupId>
    <artifactId>driver-api-impl</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.together</groupId>
            <artifactId>driver-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

3 com.together.learning.java.spi.Driver檔案內容

com.zhangsan.driver.ZhangshanDriverImpl

3 測試ServiceLoader

專案結構如下截圖:
service-loader-test

下面是部分檔案的定義
1 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.together</groupId>
    <artifactId>service-loader-test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.together</groupId>
            <artifactId>driver-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.zhangsan</groupId>
            <artifactId>driver-api-impl</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

2 Test.java

package com.together.test;

import com.together.learning.java.spi.Driver;

import java.util.ServiceLoader;

/**
 * @author jiangjian
 */
public class Test {
    public static void main(String[] args) {
        ServiceLoader<Driver> drivers = ServiceLoader.load(Driver.class);
        for(Driver driver : drivers) {
            System.out.println(driver.getVendorName());
        }
    }
}

至此,ServiceLoader大致的功能介紹完了,有興趣的可以進一步閱讀ServiceLoader原始碼,裡面的LazyIterator這種實現還是值得學習和借鑑的.