深入剖析Spring boot自動裝配原理

    ServiceLoader是jdk6里面引進的一個特性。它用來實現(xiàn)SPI(Service Provider Interface),一種服務發(fā)現(xiàn)機制,很多框架用它來做來做服務的擴展發(fā)現(xiàn)。

系統(tǒng)里抽象的各個模塊一般會有很多種不同的實現(xiàn),如JDBC、日志等。通常模塊之間我們均是基于接口進行編程,而不是對實現(xiàn)類進行硬編碼。這時候就需要一種動態(tài)替換發(fā)現(xiàn)的機制,即在運行時動態(tài)地給接口添加實現(xiàn),而不需要在程序中指明。

引用自JDK文檔對于java.util.ServiceLoader的描述:

      服務是一個熟知的接口和類(通常為抽象類)集合。服務提供者是服務的特定實現(xiàn)。提供者中的類通常實現(xiàn)接口,并子類化在服務本身中定義的子類。服務提供者可以以擴展的形式安裝在 Java 平臺的實現(xiàn)中,也就是將 jar 文件放入任意常用的擴展目錄中。也可通過將提供者加入應用程序類路徑,或者通過其他某些特定于平臺的方式使其可用。

      為了加載,服務由單個類型表示,也就是單個接口或抽象類。一個給定服務的提供者包含一個或多個具體類,這些類擴展了此服務類型,具有特定于提供者的數(shù)據(jù)和代碼。提供者類通常不是整個提供者本身而是一個代理,它包含足夠的信息來決定提供者是否能滿足特定請求,還包含可以根據(jù)需要創(chuàng)建實際提供者的代碼。提供者類的詳細信息高度特定于服務;任何單個類或接口都不能統(tǒng)一它們,因此這里沒有定義任何這種類型。此設施唯一強制要求的是,提供者類必須具有不帶參數(shù)的構造方法,以便它們可以在加載中被實例化。

當服務的提供者提供了服務接口的一種實現(xiàn)之后,在jar包的META-INF/services/ 目錄里同時創(chuàng)建一個以服務接口命名的該服務接口具體實現(xiàn)類文件。當外部程序裝配該模塊時,通過該jar包META-INF/services/里的配置文件找到具體的實現(xiàn)類名,從而完成模塊的注入,而不需要在代碼里定制。

在JDBC中使用了ServiceLoader對不同數(shù)據(jù)庫驅(qū)動進行加載。

DriverManager通過ServiceLoader加載數(shù)據(jù)庫驅(qū)動,JDBC原理可移步【JDBC原理】。

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

ServiceLoader 使用小栗子

首先定義一個SPIService接口

package com.study;

public interface SPIService {
 
    void excute();
}

再定義兩個實現(xiàn)類

package com.study.impl;
import com.study.SPIService;

public class SPIServiceImpl1 implements SPIService {
 
  @Override
  public void excute() {
    System.out.println("SPIServiceImpl1");
  }
}

package com.study.impl;
import com.study.SPIService;

public class SPIServiceImpl2 implements SPIService {
 
  @Override
  public void excute() {
    System.out.println("SPIServiceImpl2");
  }
}

在ClassPath路徑下配置添加文件,META-INF/services/com.study.SPIService,文件名為接口的全限定類名。在配置文件中加入兩個實現(xiàn)類的全限定類名。

com.study.impl.SPIServiceImpl1
com.study.impl.SPIServiceImpl2

寫一個測試類SPITest.java

public class SPITest {
  public static void main(String[] args) {
    ServiceLoader<SPIService> loaders = ServiceLoader.load(SPIService.class);
    Iterator<SPIService> it = loaders.iterator();
    while (it.hasNext()) {
      SPIService spiSer= it.next();
      spiSer.excute();
    }
  }
}

運行結果:

SPIServiceImpl1
SPIServiceImpl2

ServiceLoader的load方法將在META-INF/services/com.study.SPIService中配置的子類都進行了加載。

ServiceLoader的核心源碼分析

public final class ServiceLoader<S> implements Iterable<S>{
    // 需要加載的資源的配置文件路徑
    private static final String PREFIX = "META-INF/services/";
    // 加載的服務類或接口
    private Class<S> service;
    // 類加載時用到的類加載器
    private ClassLoader loader;
    // 基于實例的已加載的順序緩存類,其中Key為實現(xiàn)類的全限定類名
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
    // "懶查找"迭代器,ServiceLoader的核心
    private LazyIterator lookupIterator;
 
    public void reload() {
        // 清空緩存
        providers.clear();
        // 構造LazyIterator實例
        lookupIterator = new LazyIterator(service, loader);
    }
     
    // 私有構造方法
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = svc;
        loader = cl;
        reload();
    }
}

ServiceLoader只有一個私有的構造函數(shù),也就是它不能通過構造函數(shù)實例化,但是要實例化ServiceLoader必須依賴于它的靜態(tài)方法調(diào)用私有構造去完成實例化操作。

來看ServiceLoader的提供的靜態(tài)方法,這幾個方法都可以用于構造ServiceLoader的實例。

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 獲取當前線程的線程上下文類加載器實例,確保通過此classLoader也能加載到項目中的資源文件
    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);
}
 
public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    ClassLoader prev = null;
    while (cl != null) {
        prev = cl;
        cl = cl.getParent();
    }
    return ServiceLoader.load(service, prev);
}

load(Class<S> service, ClassLoader loader)是典型的靜態(tài)工廠方法,直接調(diào)用ServiceLoader的私有構造器進行實例化,除了需要指定加載類的目標類型,還需要傳入類加載器的實例。load(Class<S>service)實際上也是委托到load(Class<S> service, ClassLoader loader),不過它使用的類加載器指定為線程上下文類加載器,一般情況下線程上下文類加載器獲取到的就是應用類加載器(系統(tǒng)類加載器)。loadInstalled(Class<S> service)方法又看出了"雙親委派模型"的影子,它指定類加載器為最頂層的啟動類加載器,最后也是委托到load(Class<S> service, ClassLoader loader)。






這里重點關注為什么類加載器使用線程上下文類加載器?以JDBC加載MySQL驅(qū)動舉例。

Java類加載的過程通常是遵循雙親委派模型的。但是對于SPI接口實現(xiàn)類的加載就需要破壞雙親委派模型。

首先java.sql.DriverManager是由啟動類加載器加載的,創(chuàng)建真正的Dirver對象時需要使用到mysql提供的實現(xiàn):com.mysql.jdbc.Dirver,即要初始化該類。但是啟動類加載器加載DirverManager的時候,使用到了啟動類加載器無法加載的類,這時候就需要由系統(tǒng)類加載器來加載。com.mysql.jdbc.Dirver通常放在類路徑下的(其實不一定)。到這里和線程上下文類加載器沒由任何關系。在DriverManager中使用系統(tǒng)類加載的時候,可以直接使用靜態(tài)方法ClassLoader.getSystemClassLoader(),但是這種情況的前提是com.mysql.jdbc.Dirver類在類路徑下。如果不在類路徑下,而且在系統(tǒng)環(huán)境中有其他的類加載器,在通過其他類加載器可能出現(xiàn)無法正確加載擴展點的情況。比如某個類的字節(jié)碼是在數(shù)據(jù)庫中存儲,這時我們需要自定義一個類加載器去加載它,這個類加載器會告訴DriverManager去我們指定放的地方取。因此Thread.currentThread().setContextClassLoader(自定義加載器/默認是系統(tǒng)類加載器); 這個就是線程上下文類加載器起到的介質(zhì)作用。線程上下文中默認放的是系統(tǒng)類加載器。

ServiceLoader實現(xiàn)了Iterable接口。

public Iterator<S> iterator() {
    // Iterator的匿名實現(xiàn)
    return new Iterator<S>() {
        // 基于實例的已加載的順序緩存類Map的Entry迭代器實例
        Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
         
        // 先從緩存中判斷是否有下一個實例,否則通過懶加載迭代器LazyIterator去判斷是否存在下一個實例
        public boolean hasNext() {
            if (knownProviders.hasNext())
                return true;
            return lookupIterator.hasNext();
        }
         
        // 如果緩存中判斷是否有下一個實例,如果有則從緩存中的值直接返回,否則通過懶加載迭代器LazyIterator獲取下一個實例
        public S next() {
            if (knownProviders.hasNext())
                return knownProviders.next().getValue();
            return lookupIterator.next();
        }
         
        // 不支持移除
        public void remove() {
            throw new UnsupportedOperationException();
        }
 
    };
}

LazyIterator本身也是一個Iterator接口的實現(xiàn),它是ServiceLoader的一個私有內(nèi)部類。

private class LazyIterator implements Iterator<S>
{
 
    Class<S> service;
    ClassLoader loader;
    // 加載的資源的URL集合
    Enumeration<URL> configs = null;
    // 所有需要加載的實現(xiàn)類的全限定類名的集合
    Iterator<String> pending = null;
    // 下一個需要加載的實現(xiàn)類的全限定類名
    String nextName = null;
 
    private LazyIterator(Class<S> service, ClassLoader loader) {
        this.service = service;
        this.loader = loader;
    }
 
    public boolean hasNext() {
        // 如果下一個需要加載的實現(xiàn)類的全限定類名不為null,則說明資源中存在內(nèi)容
        if (nextName != null) {
            return true;
        }
        // 如果加載的資源的URL集合為null則嘗試進行加載
        if (configs == null) {
            try {
                // 資源的名稱,META-INF/services/ + '需要加載的類的全限定類名'
                String fullName = PREFIX + service.getName();
                if (loader == null)
                    configs = ClassLoader.getSystemResources(fullName);
                else
                    // 從ClassPath加載資源
                    configs = loader.getResources(fullName);
            } catch (IOException x) {
                fail(service, "Error locating configuration files", x);
            }
        }
        // 從資源中解析出需要加載的所有實現(xiàn)類的全限定類名
        while ((pending == null) || !pending.hasNext()) {
            if (!configs.hasMoreElements()) {
                return false;
            }
            pending = parse(service, configs.nextElement());
        }
        // 獲取下一個需要加載的實現(xiàn)類的全限定類名
        nextName = pending.next();
        return true;
    }
 
    public S next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        String cn = nextName;
        nextName = null;
        try {
            // 反射構造Class<S>實例,同時進行初始化,并且強制轉(zhuǎn)化為對應的類型的實例
            S p = service.cast(Class.forName(cn, true, loader).newInstance());
            // 添加進緩存,Key為實現(xiàn)類的全限定類名,Value為實現(xiàn)類的實例
            providers.put(cn, p);
            return p;
        } catch (ClassNotFoundException x) {
            fail(service, "Provider " + cn + " not found");
        } catch (Throwable x) {
            fail(service, "Provider " + cn + " could not be instantiated: " + x, x);
        }
        throw new Error();          // This cannot happen
    }
 
    public void remove() {
        throw new UnsupportedOperationException();
    }
 
}
ServiceLoader總結

JDK提供了一種幫第三方實現(xiàn)者加載服務的便捷方式,如JDBC、日志等,第三方實現(xiàn)者需要遵循約定把具體實現(xiàn)的類名放在/META-INF里。當JDK啟動時會去掃描所有jar包里符合約定的類名,再調(diào)用forName進行加載,如果JDK的ClassLoader無法加載,就使用當前執(zhí)行線程的線程上下文類加載器。

但是在通過SPI查找服務的具體實現(xiàn)時,會遍歷所有的實現(xiàn)進行實例化,并在循環(huán)中找到需要的具體實現(xiàn)。

作者:java知路


歡迎關注微信公眾號 :java知路