深入剖析Spring boot自動(dòng)裝配原理

    ServiceLoader是jdk6里面引進(jìn)的一個(gè)特性。它用來實(shí)現(xiàn)SPI(Service Provider Interface),一種服務(wù)發(fā)現(xiàn)機(jī)制,很多框架用它來做來做服務(wù)的擴(kuò)展發(fā)現(xiàn)。

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

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

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

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

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

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

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

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

ServiceLoader 使用小栗子

首先定義一個(gè)SPIService接口

package com.study;

public interface SPIService {
 
    void excute();
}

再定義兩個(gè)實(shí)現(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,文件名為接口的全限定類名。在配置文件中加入兩個(gè)實(shí)現(xiàn)類的全限定類名。

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

寫一個(gè)測(cè)試類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();
    }
  }
}

運(yùn)行結(jié)果:

SPIServiceImpl1
SPIServiceImpl2

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

ServiceLoader的核心源碼分析

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

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

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

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 獲取當(dāng)前線程的線程上下文類加載器實(shí)例,確保通過此classLoader也能加載到項(xiàng)目中的資源文件
    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的私有構(gòu)造器進(jìn)行實(shí)例化,除了需要指定加載類的目標(biāo)類型,還需要傳入類加載器的實(shí)例。load(Class<S>service)實(shí)際上也是委托到load(Class<S> service, ClassLoader loader),不過它使用的類加載器指定為線程上下文類加載器,一般情況下線程上下文類加載器獲取到的就是應(yīng)用類加載器(系統(tǒng)類加載器)。loadInstalled(Class<S> service)方法又看出了"雙親委派模型"的影子,它指定類加載器為最頂層的啟動(dòng)類加載器,最后也是委托到load(Class<S> service, ClassLoader loader)。






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

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

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

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

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

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

private class LazyIterator implements Iterator<S>
{
 
    Class<S> service;
    ClassLoader loader;
    // 加載的資源的URL集合
    Enumeration<URL> configs = null;
    // 所有需要加載的實(shí)現(xiàn)類的全限定類名的集合
    Iterator<String> pending = null;
    // 下一個(gè)需要加載的實(shí)現(xiàn)類的全限定類名
    String nextName = null;
 
    private LazyIterator(Class<S> service, ClassLoader loader) {
        this.service = service;
        this.loader = loader;
    }
 
    public boolean hasNext() {
        // 如果下一個(gè)需要加載的實(shí)現(xiàn)類的全限定類名不為null,則說明資源中存在內(nèi)容
        if (nextName != null) {
            return true;
        }
        // 如果加載的資源的URL集合為null則嘗試進(jìn)行加載
        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);
            }
        }
        // 從資源中解析出需要加載的所有實(shí)現(xiàn)類的全限定類名
        while ((pending == null) || !pending.hasNext()) {
            if (!configs.hasMoreElements()) {
                return false;
            }
            pending = parse(service, configs.nextElement());
        }
        // 獲取下一個(gè)需要加載的實(shí)現(xiàn)類的全限定類名
        nextName = pending.next();
        return true;
    }
 
    public S next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }
        String cn = nextName;
        nextName = null;
        try {
            // 反射構(gòu)造Class<S>實(shí)例,同時(shí)進(jìn)行初始化,并且強(qiáng)制轉(zhuǎn)化為對(duì)應(yīng)的類型的實(shí)例
            S p = service.cast(Class.forName(cn, true, loader).newInstance());
            // 添加進(jìn)緩存,Key為實(shí)現(xiàn)類的全限定類名,Value為實(shí)現(xiàn)類的實(shí)例
            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總結(jié)

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

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

作者:java知路


歡迎關(guān)注微信公眾號(hào) :java知路