1. 程式人生 > >springboot原始碼分析4-springboot之SpringFactoriesLoader使用

springboot原始碼分析4-springboot之SpringFactoriesLoader使用

摘要:本文我們重點分析一下Spring框架中的SpringFactoriesLoader類以及META-INF/spring.factories的使用。在詳細分析之前,我們可以思考一個問題?在我們設計一套API供別人呼叫的時候,如果同一個功能的要求特別多,或者同一個介面要面對很複雜的業務場景,這個時候我們該怎麼辦呢?其一:我們可以規範不同的系統呼叫,也就是傳遞一個系統標識;其二:我們在內部編碼的時候可以使用不同的條件判斷語句進行處理;其三:我們可以寫幾個策略類來來應對這個複雜的業務邏輯,比如同一個功能的實現,A實現類與B實現類的邏輯一點也不一樣,但是目標是一樣的,這個時候使用策略類是毋庸置疑的?上述的問題我們是在API層面進行處理的?那萬一有一天讓我們自己設計一套框架,然後讓別人直接使用?我們該如何處理上述的這個問題呢?這個可能就涉及到SPI的一些規範了跟技巧了,比如同一套API可能有很多實現類,這個時候我們該如何內建一系列實現類供框架使用呢?或者讓使用者也可以自定義這些API的實現類,相互之間協作運轉。帶著這些問題我們看一下Spring框架中的SpringFactoriesLoader以及META-INF/spring.factories的使用。

1.1 屬性配置

首先,我們來看一下屬性的配置方式,在傳統的開發模式中(無springboot),屬性檔案的格式無外乎就是兩種,第一種是XML,第二種是key、value形式(properties檔案)。當然springboot引入了yaml方式。這裡我們重點看一下XML以及properties的定義以及獲取方式。

1.1.1 properties方式

1.1.1.1. 單個屬性配置

首先,我們新建一個shareniu-single.factories檔案,該檔案的目錄結構如下圖所示:

                     

shareniu-single.factories的內容如下:

shareniu=http://www.shareniu.com/

1.1.1.2. 多個屬性配置

單個屬性的定義比較簡單,就是key、value形式即可。對於同一個屬性有多個值的定義格式如下:

com.example.demo.ch3.IShareniu=\

com.example.demo.ch3.ShareniuA,\

com.example.demo.ch3.ShareniuB

1.1.1.3. properties讀取工具類

上述的屬性定義完畢之後,我們寫一個工具類進行測試,在這裡我們直接呼叫了PropertiesLoaderUtils類中的方法。例項程式碼如下:

1 public class PropertiesLoaderUtilsTest {

2  public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/shareniu-single.factories";

3  public static void main(String[] args) throws IOException {

4  Properties properties = PropertiesLoaderUtils.loadAllProperties(FACTORIES_RESOURCE_LOCATION,null);

5  System.out.println(properties);

6  }

7 }

上述的程式碼直接呼叫了PropertiesLoaderUtils類中的loadAllProperties方法,PropertiesLoaderUtils的全路徑名稱為:org.springframework.core.io.support.PropertiesLoaderUtils。該類位於spring-core-5.0.0.RC3.jar包中。

執行上面的程式碼,程式的輸出如下:

{shareniu=http://www.shareniu.com/, com.example.demo.ch3.IShareniu=com.example.demo.ch3.ShareniuA,com.example.demo.ch3.ShareniuB}

果真我們自定義的屬性都可以完美的獲取到。

關於PropertiesLoaderUtils.loadAllProperties的核心程式碼如下:

1 public static Properties loadAllProperties(String resourceName, @Nullable ClassLoader classLoader) throws IOException {

2  ClassLoader classLoaderToUse = classLoader;

3  if (classLoaderToUse == null) {

4  classLoaderToUse = ClassUtils.getDefaultClassLoader();

5  }

6  Enumeration<URL> urls = (classLoaderToUse != null ? classLoaderToUse.getResources(resourceName) :

7  ClassLoader.getSystemResources(resourceName));

8  Properties props = new Properties();

9  while (urls.hasMoreElements()) {

10  URL url = urls.nextElement();

11  URLConnection con = url.openConnection();

12  ResourceUtils.useCachesIfNecessary(con);

13  InputStream is = con.getInputStream();

14  try {

15  if (resourceName.endsWith(XML_FILE_EXTENSION)) {

16  props.loadFromXML(is);

17  }

18  else {

19  props.load(is);

20  }

21  }

22  finally {

23  is.close();

24  }

25  }

26  return props;

27  }

loadAllProperties方法,首先會根據類載入器去獲取指定的資源(也就是我們呼叫的時候,傳遞的resourceName引數值)。然後判斷資源的字尾是否為xml,如果字尾是xml則使用xml方式載入資源,否則都是用Properties方式進行資源的載入。

注意:雖然上述的程式碼我們指定的資源名稱是:META-INF/shareniu-single.factories,但是上述的類載入器不僅掃描我們專案的META-INF/shareniu-single.factories,還會掃描當前類載入所載入的jar包中的META-INF/shareniu-single.factories檔案。

1.1.2 xml方式

瞭解了上述程式碼的處理邏輯之後,我們看一下xml方式如何定義,shareniu.xml定義內容如下:

1 <?xml version="1.0" encoding="UTF-8"?>  

2 <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">    

3 <properties>   

4     <comment>shareniu xml配置檔案</comment>  

5     <entry key="username">shareniu</entry>  

6     <entry key="url">http://www.shareniu.com/</entry>   

7 </properties>

執行上述的屬性工具類,控制檯的輸出資訊如下:

{url=http://www.shareniu.com/, username=shareniu}

1.2 SpringFactoriesLoader使用

瞭解了XML以及properties的定義以及獲取方式之後,接下來學習SpringFactoriesLoader類就簡單的多了。

首先,看一下SpringFactoriesLoader類定義的方法如下所示:

 

1. loadFactoryNames:載入指定的factoryClass並進行例項化。

2. loadSpringFactories:載入指定的factoryClass。

3. instantiateFactory:對指定的factoryClass進行例項化。

    通過上文可知:loadFactoryNames方法內部直接呼叫loadSpringFactories方法,loadSpringFactories方法則會呼叫instantiateFactory方法。

    loadSpringFactories方法內部會載入META-INF/spring.factories檔案,這裡載入的檔案不僅包含專案中的,還包換我們專案環境所依賴的jar包中的META-INF/spring.factories檔案。

1.現在,我們寫一個簡單的測試類,載入spring.factories檔案,例項程式碼如下:

spring.factories檔案的內容如下所示:

com.example.demo.ch3.IShareniu=\

com.example.demo.ch3.ShareniuA,\

com.example.demo.ch3.ShareniuB

   其中:IShareniu為介面,ShareniuA以及ShareniuB實現了IShareniu介面。結構如下圖所示:

                           

2.自定義測試類並呼叫SpringFactoriesLoader類中的相關方法,如下所示:

1 public class DemoApplication {

2  public static void main(String[] args) {

3  List<String> loadFactoryNames = SpringFactoriesLoader.loadFactoryNames(IShareniu.class, null);

4  System.out.println(loadFactoryNames);

5  }

6 }

    自行上述程式碼,程式的輸出資訊如下:

[com.example.demo.ch3.ShareniuA, com.example.demo.ch3.ShareniuB]

通過上述的程式碼可知,我們確實完成了自身專案中META-INF/spring.factories檔案的屬性讀取。

那我們能否能夠通過Spring框架例項化這些類呢?答案是肯定的?例項程式碼如下:

1 List<IShareniu> loadFactories = SpringFactoriesLoader.loadFactories(IShareniu.class, null);

2  System.out.println(loadFactories);

   自行上述程式碼,程式的輸出資訊如下:

[com.example.demo.ch3.ShareniuA[email protected], [email protected]]

loadFactories方法返回的已經是例項化完畢的物件了。

1.3 SpringFactoriesLoader原理

接下來,我們看一下SpringFactoriesLoader類中的loadFactories方法,如下所示:

1 public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {

2  Assert.notNull(factoryClass, "'factoryClass' must not be null");

3  ClassLoader classLoaderToUse = classLoader;

4  if (classLoaderToUse == null) {

5  classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();

6  }

7  List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);

8  if (logger.isTraceEnabled()) {

9  logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);

10  }

11  List<T> result = new ArrayList<>(factoryNames.size());

12  for (String factoryName : factoryNames) {

13  result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));

14  }

15  AnnotationAwareOrderComparator.sort(result);

16  return result;

17  }

loadFactories方法首先獲取類載入器,然後呼叫loadFactoryNames方法獲取所有的制定資源的名稱集合、其次呼叫instantiateFactory方法例項化這些資源類並將其新增到result集合中。最後呼叫AnnotationAwareOrderComparator.sort方法進行集合的排序。

1.3.1 loadFactoryNames方法

loadFactoryNames方法核心程式碼如下:

18 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

19 private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {

20  MultiValueMap<String, String> result = cache.get(classLoader);

21  if (result != null)

22  return result;

23  try {

24  Enumeration<URL> urls = (classLoader != null ?

25  classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :

26  ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

27  result = new LinkedMultiValueMap<>();

28  while (urls.hasMoreElements()) {

29  URL url = urls.nextElement();

30  UrlResource resource = new UrlResource(url);

31  Properties properties = PropertiesLoaderUtils.loadProperties(resource);

32  for (Map.Entry<?, ?> entry : properties.entrySet()) {

33  List<String> factoryClassNames = Arrays.asList(

34  StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));

35  result.addAll((String) entry.getKey(), factoryClassNames);

36  }

37  }

38  cache.put(classLoader, result);

39  return result;

40  }

41  catch (IOException ex) {

42  }

43  }

loadSpringFactories方法直接載入所有的META-INF/spring.factories檔案內容,其內部還是呼叫PropertiesLoaderUtils.loadProperties方法進行處理。該方法前面我們也詳細的演示了,再次不再累贅。

唯一需要了解的是,這個地方使用了快取策略。

1.3.2 instantiateFactory方法

instantiateFactory方法的核心邏輯如下:

1  private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {

2  try {

3         Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);

4  if (!factoryClass.isAssignableFrom(instanceClass)) {

5                throw new IllegalArgumentException(

6  }

7  return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();

8  }

9  catch (Throwable ex) {

10  throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);

11  }

12  }

直接呼叫了ClassUtils.forName方法,然後呼叫ReflectionUtils.accessibleConstructor方法進行例項物件進行物件的例項化工作,原來這裡直接使用了反射技術進行物件的例項化工作。原來如此。

至此,XML以及properties的定義以及獲取方式,SpringFactoriesLoader類的使用以及原理已經講解完畢。

歡迎關注我的微信公眾號,第一時間獲得部落格更新提醒,以及更多成體系的Java相關原創技術乾貨。 
掃一掃下方二維碼或者長按識別二維碼,即可關注。
 

   

作者:分享牛        本部落格中未標明轉載的文章歸作者分享牛所有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。

相關推薦

springboot原始碼分析4-springbootSpringFactoriesLoader使用

摘要:本文我們重點分析一下Spring框架中的SpringFactoriesLoader類以及META-INF/spring.factories的使用。在詳細分析之前,我們可以思考一個問題?在我們設計一套API供別人呼叫的時候,如果同一個功能的要求特別多,或者同一個介面要面對

springboot原始碼分析5-springboot命令列引數以及原理

摘要:本文我們重點分析一下Springboot框架中的命令列引數的使用以及框架內部處理的命令列引數的原理。眾所周知,springboot專案可以有兩種方式啟動,第一種使用jar包;第二種使用war包。在使用jar方式的時候,我們可以在啟動jar包的時候設定一些命令引數。1.1

springboot原始碼分析6-springbootPropertySource類初探

在springboot原始碼分析5-springboot之命令列引數以及原理一文中,我們看到了例項化Source類的時候,會去先例項化其父類SimpleCommandLinePropertySource。SimpleCommandLinePropertySource類的建構函

SpringBoot原始碼分析---SpringBoot專案啟動類SpringApplication淺析

原始碼版本 本文原始碼採用版本為SpringBoot 2.1.0BUILD,對應的SpringFramework 5.1.0.RC1 注意:本文只是從整體上梳理流程,不做具體深入分析 SpringBoot入口類 @SpringBootAp

Springboot原始碼分析專案結構

摘要: 無論是從IDEA還是其他的SDS開發工具亦或是https://start.spring.io/ 進行解壓,我們都會得到同樣的一個pom.xml檔案 xml <?xml version="1.0" encoding="UTF-8"?> <project xm

Springboot原始碼分析jar探祕

摘要: 利用IDEA等工具打包會出現springboot-0.0.1-SNAPSHOT.jar,springboot-0.0.1-SNAPSHOT.jar.original,前面說過它們之間的關係了,接下來我們就一探究竟,它們之間到底有什麼聯絡。 檔案對比: 進入target目錄,unzip sprin

Springboot原始碼分析番外篇

摘要: 大家都知道註解是實現了java.lang.annotation.Annotation介面,眼見為實,耳聽為虛,有時候眼見也不一定是真實的。 /** * The common interface extended by all annotation types. Note that

Springboot原始碼分析EnableAspectJAutoProxy

摘要: Spring Framwork的兩大核心技術就是IOC和AOP,AOP在Spring的產品線中有著大量的應用。如果說反射是你通向高階的基礎,那麼代理就是你站穩高階的底氣。AOP的本質也就是大家所熟悉的CGLIB動態代理技術,在日常工作中想必或多或少都用過但是它背後的祕密值得我們去深思。本文主要從Spr

Springboot原始碼分析代理三板斧

摘要: 在Spring的版本變遷過程中,註解發生了很多的變化,然而代理的設計也發生了微妙的變化,從Spring1.x的ProxyFactoryBean的硬編碼島Spring2.x的Aspectj註解,最後到了現在廣為熟知的自動代理。 說明: ProxyConfig代理的相關配置類 AdvisedSupp

Springboot原始碼分析AbstractAdvisorAutoProxyCreator

摘要: Spring的代理在上層中主要分為ProxyCreatorSupport和ProxyProcessorSupport,前者是基於代理工廠,後者是基於後置處理器,也可以認為後置就是自動代理器。當spring容器中需要進行aop進行織入的bean較多時,簡單採用ProxyFacotryBean無疑會增加很

Springboot原始碼分析TargetSource

摘要: 其實我第一次看見這個東西的時候也是不解,代理目標源不就是一個class嘛還需要封裝幹嘛。。。 其實proxy代理的不是target,而是TargetSource,這點非常重要,一定要分清楚!!! 通常情況下,一個代理物件只能代理一個target,每次方法呼叫的目標也是唯一固定的target。但是,如果

Springboot原始碼分析@Transactional

摘要: 對SpringBoot有多瞭解,其實就是看你對Spring Framework有多熟悉~ 比如SpringBoot大量的模組裝配的設計模式,其實它屬於Spring Framework提供的能力。SpringBoot大行其道的今天,基於XML配置的Spring Framework的使用方式註定已成為過去

Springboot原始碼分析事務攔截和管理

摘要: 在springboot的自動裝配事務裡面,InfrastructureAdvisorAutoProxyCreator ,TransactionInterceptor,PlatformTransactionManager這三個bean都被裝配進來了,InfrastructureAdvisorAutoPr

Springboot原始碼分析事務問題

摘要: 事務在後端開發中無處不在,是資料一致性的最基本保證。要明白進事務的本質就是進到事務切面的代理方法中,最常見的是同一個類的非事務方法呼叫一個加了事務註解的方法沒進入事務。我們以cglib代理為例,由於Spring的對於cglib AOP代理的實現,進入被代理方法的時候實際上已經離開了“代理這一層殼子”,

Springboot原始碼分析Spring迴圈依賴揭祕

摘要: 若你是一個有經驗的程式設計師,那你在開發中必然碰到過這種現象:事務不生效。或許剛說到這,有的小夥伴就會大驚失色了。Spring不是解決了迴圈依賴問題嗎,它是怎麼又會發生迴圈依賴的呢?,接下來就讓我們一起揭祕Spring迴圈依賴的最本質原因。 Spring迴圈依賴流程圖 Spring迴圈依賴發生原因

Springboot原始碼分析代理物件內嵌呼叫

摘要: 關於這個話題可能最多的是@Async和@Transactional一起混用,我先解釋一下什麼是代理物件內嵌呼叫,指的是一個代理方法呼叫了同類的另一個代理方法。首先在這兒我要宣告事務直接的巢狀呼叫除外,至於為什麼,是它已經將資訊儲存線上程級別了,是不是又點兒抽象,感覺吃力,可以看看我前面關於事務的介紹。

Springboot原始碼分析TypeFilter魔力

摘要: 在平常的開發中,不知道大家有沒有想過這樣一個問題,為什麼我們自定義註解的時候要使用spring的原生註解(這裡指的是類似@Component,@Service........),要麼就是 隨便弄個註解,搭配自己的切面程式設計來實現某些業務邏輯。這篇文章主要給大家分享一下,如何脫離Spring原生註解自

SpringBoot原始碼分析 配置檔案的載入原理和優先順序

從SpringBoot原始碼分析 配置檔案的載入原理和優先順序 本文從SpringBoot原始碼分析 配置檔案的載入原理和配置檔案的優先順序     跟入原始碼之前,先提一個問題:   SpringBoot 既可以載入指定目錄下的配置檔案獲取配置項,也可

十.linux開發uboot移植(十)——uboot原始碼分析4-uboot的命令體系

一、uboot命令體系簡介 1、uboot命令體系實現程式碼在哪裡 uboot命令體系的實現程式碼在uboot/common/cmd_xxx.c中。有若干個.c檔案和命令體系有關。(還有command.c main.c也是和命令有關的)。 uboot實

菜鳥成長Thread的SetDeamon()守護執行緒原始碼分析(4)

    相信大家的童年都有過《西遊記》的陪伴,唐僧四人一起去西天取經的故事肯定也是耳熟能詳,在西遊記裡唐僧作為整個取經隊伍的領導者,而徒弟們跟隨師傅指引的方向去前進,這裡我們可以把取經當成是Thread執行的終點,師傅作為被守護執行緒,徒弟作為守護執行緒,當取完經後或者師