天天看點

SpringBoot源碼解讀之——SpringFactoriesLoader

作者:幸福積極的在路上

前言

開源分享不易,感興趣的私聊,微信加群面基,感謝大家的支援,多關注,點贊。後續也會分享更多的幹貨和技術資訊,您的閱讀就是對小編的支援,再次感謝各位老鐵!

 要想深入學習源碼,那麼就得先學會讀懂它的注釋,畢竟是一手知識嘛。大家都知道SpringBoot是一款多麼優秀的架構,它給Java開發者帶來了極大的便利,再也不用去整合SSM了,這裡就不繼續贊美SpringBoot了。相信大家都會很好奇SpringBoot底層源碼是怎麼運作的?它是怎麼神奇的讓我們可以快速開發JAVAEE企業級項目?如何快速整合第三方架構?接下來的深入學習SpringBoot源碼系列,讓我和各位小夥伴們一同學習SpringBoot底層源碼。

這裡先學習下 SpringFactoriesLoader,這個類在SpringBoot整個底層源碼中起到了非常關鍵的作用,置于有多關鍵,等讀完整片文章你們就知道了。

SpringBoot版本:2.2.1.RELEASE

正文

1. 話不多說,源碼注釋解讀一波

General purpose factory loading mechanism for internal use within the framework.

<p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
 factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
 may be present in multiple JAR files in the classpath. The {@code spring.factories}
 file must be in {@link Properties} format, where the key is the fully qualified
 name of the interface or abstract class, and the value is a comma-separated list of
 implementation class names. For example:           

翻譯過來就是:

SpringFactoriesLoader是用于Spring架構内部的通用工廠加載機制。

SpringFactoriesLoader通過loadFactories方法來加載并執行個體化來自FACTORIES_RESOUCE_LOCATION路徑中的檔案給定的工廠類型,
而這些檔案可能包含在類路徑的jar包中。這些檔案通常都命名為spring.factories,并且都是以properties屬性作為格式,檔案中key表示
的是接口或者抽象類的全限定名稱,而值是以逗号分隔的實作類的名稱清單。
           

2. 上源碼

 首先,可以看到SpringFactoriesLoader是final類,final修飾的類是不可以被繼承,類中的方法都是不可以被覆寫的,且預設都是final修飾的方法,可以猜想到SpringFactoriesLoader類在被設計之初,是不想開發者繼承該類并對該類進行擴充。是以,如果在開發中不想讓别人對你的類繼承或者擴充,那就用final來修飾吧~~

public final class SpringFactoriesLoader {
}           

下面看下SpringFactoriesLoader類有哪些成員變量?

/**
	 * 尋找工廠的位置
	 * 工廠可以存放在多個jar檔案中
	 */
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";           

先來看看spring-boot:2.2.1.RELEASEjar包下,spring.factories檔案的内容。

SpringBoot源碼解讀之——SpringFactoriesLoader
SpringBoot源碼解讀之——SpringFactoriesLoader
SpringBoot源碼解讀之——SpringFactoriesLoader

在spring.factories檔案中,有非常多的工廠類,包括了屬性源加載器、錯誤報告器、容器初始化器、容器監聽器等,這些工廠類在SpringBoot中都有非常重要的作用,具體的讀者可以自行前往檢視。

// 自定義的用于存儲工廠的緩存
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();           

 這裡的緩存是通過ConcurrentReferenceHashMap類實作的,對于這個類估計很多讀者都是第一次見吧... 下面就簡單介紹下ConcurrentReferenceHashMap這個資料結構。

通過閱讀ConcurrentReferenceHashMap的類注釋,可以總結出以下幾點:

  1. 它是一個ConcurrentHashMap,它的key和value使用ReferenceType.SOFT或者是ReferenceType.WEAK,即軟引用和弱引用。
  2. 它可以用作Collections.synchronizedMap(new WeakHashMap<K, Reference>())的替代方法。
  3. 它遵循ConcurrentHashMap相同的設計限制,但不同的是它還支援null鍵和null值。
  4. 它預設使用的是SoftReference軟引用。
  5. 使用軟引用就意味着,在進行下一次GC時,如果即将發生OOM,GC就會把軟引用指向的對象給回收掉。這一特性适合用作緩存處理。

2.1 loadFactories方法

 下面先來看下loadFactories方法的注釋。

/**
	 * Load and instantiate the factory implementations of the given type from
	 * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
	 * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
	 * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
	 * to obtain all registered factory names.
	 * @param factoryType the interface or abstract class representing the factory
	 * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
	 * @throws IllegalArgumentException if any factory implementation class cannot
	 * be loaded or if an error occurs while instantiating any factory
	 * @see #loadFactoryNames
	 */
           

翻譯過來就是: loadFactories方法通過類加載器來加載并且執行個體化FACTORIES_RESOURCE_LOCATION路徑檔案中定義的工廠實作。在傳回工廠之前,都會通過AnnotationAwareOrderComparator這個類來進行排序。如果需要自定義執行個體化政策,請使用loadFactoryNames去擷取所有注冊的工廠名稱。 loadFactories方法中,入參factoryType表示工廠類的接口或者抽象類;入參classLoader表示加載工廠的類加載器,如果為空則會使用預設的類加載器。

public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
    Assert.notNull(factoryType, "'factoryType' must not be null");
    // 類加載器
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
      // 如果為空則用系統預設類加載器
      classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    // 通過擷取所有工廠實作類的名稱集合
    List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
    if (logger.isTraceEnabled()) {
      logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
    }
    List<T> result = new ArrayList<>(factoryImplementationNames.size());
    for (String factoryImplementationName : factoryImplementationNames) {
      // 執行個體化工廠實作類,然後添加進result集合中
      result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
    }
    // 通過AnnotationAwareOrderComparator#sort方法對工廠名稱進行排序
    AnnotationAwareOrderComparator.sort(result);
    return result;
  }

  private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
    try {
      // 通過classUtils工具類擷取工廠實作類的Class對象
      Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
      if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
        throw new IllegalArgumentException(
            "Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
      }
      // 通過反射工具建立工廠類執行個體對象
      return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
    }
    catch (Throwable ex) {
      throw new IllegalArgumentException(
        "Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
        ex);
    }
  }

           
 可以看到,loadFactories方法邏輯還是比較簡單的,作用也比較明确,即:① 通過classLoader去加載工廠擷取其對應類名稱;② 通過instantiateFactory方法執行個體化工廠類;③ 通過AnnotationAwareOrderComparator#sort方法對工廠進行排序;           

看下哪些地方用到了loadFactories方法:

SpringBoot源碼解讀之——SpringFactoriesLoader

上圖中的ConfigFileApplicationListener類是用于加載application配置檔案的監聽器類,對于application配置檔案的加載,後面會詳細講解~~

2.2 loadFactoryNames方法

 由于loadFactoryNames方法的注釋和loadFactories内容一樣,是以這裡就不寫出來了。

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    // 擷取到factoryType工廠類型
    String factoryTypeName = factoryType.getName();
    // 加載SpringFactories,如果沒有則傳回一個空集合
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
  }

  private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    // 從緩存中擷取已經加載過的SpringFactories
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
      return result;
    }

    try {
      Enumeration<URL> urls = (classLoader != null ?
          // 通過類加載器讀取類路徑下的spring.factories檔案,然後封裝成URL存儲于Enumeration中
          classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
          ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      result = new LinkedMultiValueMap<>();
      // 周遊urls,再将url封裝成UrlResource對象
      while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        UrlResource resource = new UrlResource(url);
        /**
         *  通過PropertiesLoaderUtils屬性加載器去加載spring.factories中的value值。
         *  這裡的Properties是繼承了HashTable的一個屬性,key和value就對應着spring.factories檔案裡的key和value。
         *  在PropertiesLoaderUtils中,底層是通過IO流讀取的檔案資料,這裡就不細說了。
         */ 
        Properties properties = PropertiesLoaderUtils.loadProperties(resource);
        for (Map.Entry<?, ?> entry : properties.entrySet()) {
          String factoryTypeName = ((String) entry.getKey()).trim();
          // 周遊擷取工廠實作類名稱
          for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
            result.add(factoryTypeName, factoryImplementationName.trim());
          }
        }
      }
      // 将擷取結果存入緩存中
      cache.put(classLoader, result);
      return result;
    }
    catch (IOException ex) {
      throw new IllegalArgumentException("Unable to load factories from location [" +
          FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
  }           

通過IDEA來看下有哪些地方用到了loadFactoryName的方法:

SpringBoot源碼解讀之——SpringFactoriesLoader

可以看到上面圖檔中SpringApplication中是哪個地方用到了loadFactoryNames方法, 進入到SpringApplication的構造器中,會調用setInitializers方法,這個方法是用于設定初始化器,初始化器也是非常重要的一個知識點,後面會詳細介紹。

可以看到,在SpringApplication中的getSpringFactoriesInstance方法中,調用了SpringFactoriesLoader#loadFacotyNames。SpringFactoriesLoader#loadFactoryNames方法調用完之後就擷取到了spring.factories中的value值,并存儲到Set集合中,接着就調用createSpringFactoriesInstances方法通過反射工具執行個體化Set集合中存儲的工廠類,經過排序之後再傳回給上一層調用。

下面用一張簡單的書序圖描述下SpringFactoriesLoader在SpringBoot中的調用過程。

3. 總結

學習完SpringFactoriesLoader源碼之後,算是真正踏入學習SpringBoot源碼的大門了,通過SpringFactoriesLoader,SpringBoot得以去加載類路徑下jar包中的spring.factories檔案,才能夠讀取該檔案中的各種工廠類:包括監聽器、初始化器、屬性源加載器等,而通過這些工廠類,才得以讓SpringBoot變得這麼強大!!!接下來幾篇關于SpringBoot源碼将會對監聽器、初始化器、屬性源以及配置加載等機制進行深入的分析~~

作者介紹:

曾先後在網際網路金融、支付公司、阿裡巴巴、滴滴擔任技術專家角色,擅長分布式、高并發、海量資料、支付資金相關的架構,掘金簽約作者,阿裡雲專家部落客

搜尋關注公衆号:碼代碼的小司機,回複面試資料,領取大禮包!

SpringBoot源碼解讀之——SpringFactoriesLoader