java spi 深入研究以及 ClassLoader

jopen 9年前發布 | 13K 次閱讀 Java開發 Java

SPI 全稱為 (Service Provider Interface) ,是JDK內置的一種服務提供發現機制。

具體 SPI 的介紹詳細看下面的資料:

Java SPI機制簡介

Thread.currentThread().getContextClassLoader()

類加載器的簡單介紹看如下資料:

Java 類加載器

線程上下文類加載器(context class loader)是從 JDK 1.2 開始引入的。類 java.lang.Thread 中的方法 getContextClassLoader() 和 setContextClassLoader(ClassLoader cl) 用來獲取和設置線程的上下文類加載器。如果沒有通過 setContextClassLoader(ClassLoader cl)方法進行設置的話,線程將繼承其父線程的上下文類加載器。Java 應用運行的初始線程的上下文類加載器是系統類加載器。在線程中運行的代碼可以通過此類加載器來加載類和資源。

為什么會有線程上下文類加載器

Thread context class loader 存在的目的主要是為了解決 parent delegation 機制下無法干凈的解決的問題。

假如有下述委派鏈:

ClassLoader A -> System class loader -> Extension class loader -> Bootstrap class loader

那么委派鏈左邊的 ClassLoader 就可以很自然的使用右邊的 ClassLoader 所加載的類。

但如果情況要反過來,是右邊的 Bootstrap class loader 所加載的代碼需要反過來去找委派鏈靠左邊的 ClassLoader A 去加載東西怎么辦呢?沒轍,parent delegation 是單向的,沒辦法反過來從右邊找左邊。

這種情況下就可以把某個位于委派鏈左邊的 ClassLoader 設置為線程的 context class loader,這樣就給機會讓代碼不受 parent delegation 的委派方向的限制而加載到類了。

例子

JDBC 接口,我在 classpath 下面引入了 jdbc 的 jar 包。

看如下代碼:

public class TestMain {
    public static void main(String[] args) throws Exception {
        Enumeration<Driver> dEnumeration = DriverManager.getDrivers();

    while (dEnumeration.hasMoreElements()) {
        Driver driver = (Driver) dEnumeration.nextElement();
        System.out.println(driver.getClass() + " : " + driver.getClass().getClassLoader());
    }

    System.out.println(Thread.currentThread().getContextClassLoader());
    System.out.println(DriverManager.class.getClassLoader());
}

}</pre>
輸出結果如下:

class com.mysql.jdbc.Driver : sun.misc.Launcher$AppClassLoader@2e5f8245
class com.mysql.fabric.jdbc.FabricMySQLDriver : sun.misc.Launcher$AppClassLoader@2e5f8245
sun.misc.Launcher$AppClassLoader@2e5f8245
null

我們從結果可以看到,我們并沒有注冊 Driver 然后就可以獲得到 Dirver,并且還可以看到他們的類加載器都是系統加載器。而 DriverManager 的類加載器是 null,也就是說他的加載器是 Bootstrap class loader。

那我們看下 DriverManager 源碼:

static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

private static void loadInitialDrivers() { ... 省略 AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() {

            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator driversIterator = loadedDrivers.iterator();

            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
            // Do nothing
            }
            return null;
        }
    });

    ... 略
}</pre><br />

看主要的部分就是我們使用 SPI 的過程,先加載實現了 Driver 的實現類。

看下 ServiceLoader 是如何進行加載的。

public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();// 使用上下文類加載器,加載器鏈的逆向使用
        return ServiceLoader.load(service, cl);
    }

public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); }</pre>

我們創建了一個 ServiceLoader 實例:

下面是 ServiceLoader 的屬性:

  private static final String PREFIX = "META-INF/services/";//SPI 默認讀的路徑

// The class or interface representing the service being loaded
private Class<S> service;//服務的 Class 對象

// The class loader used to locate, load, and instantiate providers
private ClassLoader loader;// 類加載器,傳入的是線程上下文類加載器

// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 服務的實現者

// The current lazy-lookup iterator
private LazyIterator lookupIterator;</pre><br />

下面是構造方法以及 reload 方法:

public void reload() {
        providers.clear();//清空了 map
        lookupIterator = new LazyIterator(service, loader);// 創建 LazyIterator 迭代器
    }

private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = svc;
    loader = cl;
    reload();// 當創建實例的時候會調用這個 reload 方法。
}</pre><br />

看下 LazyIterator 迭代器的實現,從名字就可以看出來是一個懶加載的迭代器。

private class LazyIterator
        implements Iterator<S>
    {

    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null;

    private LazyIterator(Class<S> service, ClassLoader loader) {
        this.service = service;
        this.loader = loader;
    }

    public boolean hasNext() {
        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;
    }

    public S next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        String cn = nextName;
        nextName = null;
        Class<?> c = null;
        try {
            c = Class.forName(cn, false, loader);
        } catch (ClassNotFoundException x) {
            fail(service,
                 "Provider " + cn + " not found");
        }
        if (!service.isAssignableFrom(c)) {
            fail(service,
                 "Provider " + cn  + " not a subtype");
        }
        try {
            S p = service.cast(c.newInstance());
            providers.put(cn, p);
            return p;
        } catch (Throwable x) {
            fail(service,
                 "Provider " + cn + " could not be instantiated",
                 x);
        }
        throw new Error();          // This cannot happen
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }

}</pre><br />

下面看一下 ServiceLoader 的 iterator 方法的實現:

public Iterator<S> iterator() {
        return new Iterator<S>() {

        Iterator<Map.Entry<String,S>> knownProviders
            = providers.entrySet().iterator();

        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }

        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    };
}</pre><br />

可以看出來對 LazyIterator 進行了一層包裝,每次在迭代的時候會把發現的提供者加入到 ServiceLoader 內部的一個 map 當中。

回到 JDBC 的那個例子當中,為什么我們會自動進行注冊呢,這個需要看一下 LazyIterator 的 next 方法執行了如下:

c = Class.forName(cn, false, loader); 

同時我們在 DriverManager 當中也進行的迭代。最關鍵的就是 JDBC 規范了,Driver 的實現必須在初始化的時候進行注冊。下面看 mysql 里面的實現就清楚了:

static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

參考資料】

  1. http://hllvm.group.iteye.com/group/topic/38709
  2. http://www.ibm.com/developerworks/cn/java/j-lo-classloader/
  3. http://www.ibm.com/developerworks/cn/java/j-dyn0429/
  4. </ol>

    ---EOF---

    來自: http://renchx.com/java-spi-serviceloader/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!