【Spring源碼--IOC容器的實現】(二)BeanDefinition的Resource定位

  • 前言

    1.上一篇我們說到了refresh()方法,這個是容器初始化的入口。我們知道,容器初始化共有三個階段:Resource定位,BeanDefinition解析,BeanDefinition註冊。今天我們要看的是Resource定位。
    2.重要提示:這三個過程是緊密聯繫的,可以說講解了Resource定位的源碼,其他兩個過程的源碼都出來了。所以我們今天只看Resource定位,對於其他不多做研究。因爲:先走通主線,再關注細節。
    3.理解下組合複用的設計模式。舉例:ApplicationContext接口繼承了ListableBeanFactory,HierarchicalBeanFactory等等接口,那麼就意味着 ApplicationContext可以提供這些接口中定義的所有功能,但是這些功能的實際實現並不是由ApplicationContext的實現類提供的,而是以組合複用的方式委託給了各個接口的實際實現類來完成。
  • BeanDefinition的Resource定位

    接着上一篇的refresh()方法,這裏是進入了AbstractApplicationContext的refresh()方法。(大家理解下組合複用,這裏我理解的也不是很清楚)。我們來看下這個refresh()方法,這個方法很長,大家先只關心關鍵代碼。

    代碼1.1:AbstractApplicationContext的refresh()方法

    1. public void refresh() throws BeansException, IllegalStateException {
    2. synchronized (this.startupShutdownMonitor) {
    3. //調用容器準備刷新的方法,獲取 容器的當時時間,同時給容器設置同步標識.
    4. prepareRefresh();
    5. //告訴子類啓動refreshBeanFactory()方法,
    6. //Bean定義資源文件的載入從子類的refreshBeanFactory()方法啓動【重點】
    7. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    8. //爲BeanFactory配置容器特性,例如類加載器、事件處理器等
    9. prepareBeanFactory(beanFactory);
    10. try {
    11. //爲容器的某些子類指定特殊的BeanPost事件處理器
    12. postProcessBeanFactory(beanFactory);
    13. //調用所有註冊的BeanFactoryPostProcessor的Bean
    14. invokeBeanFactoryPostProcessors(beanFactory);
    15. //爲BeanFactory註冊BeanPost事件處理器.BeanPostProcessor是Bean後置處理器,用於監聽容器觸發的事件
    16. registerBeanPostProcessors(beanFactory);
    17. //初始化信息源,和國際化相關.
    18. initMessageSource();
    19. //初始化容器事件傳播器.
    20. initApplicationEventMulticaster();
    21. //調用子類的某些特殊Bean初始化方法
    22. onRefresh();
    23. //爲事件傳播器註冊事件監聽器.
    24. registerListeners();
    25. //初始化所有剩餘的單態Bean.
    26. finishBeanFactoryInitialization(beanFactory);
    27. //初始化容器的生命週期事件處理器,併發布容器的生命週期事件
    28. finishRefresh();
    29. }
    30. catch (BeansException ex) {
    31. // Destroy already created singletons to avoid dangling resources.
    32. destroyBeans();
    33. // Reset 'active' flag.
    34. cancelRefresh(ex);
    35. // Propagate exception to caller.
    36. throw ex;
    37. }
    38. }
    39. }

    上面這個方法是整個IOC容器的初始化全過程,那麼我們先關注obtainFreshBeanFactory()這個方法,因爲這個方法裏面會實現我們上述的Resource定位及其他兩個過程。來看下這個方法的源碼:

    代碼1.2:AbstractApplicationContext的obtainFreshBeanFactory()方法

    1. protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    2. refreshBeanFactory();
    3. ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    4. if (logger.isDebugEnabled()) {
    5. logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
    6. }
    7. return beanFactory;
    8. }

    上面代碼可以看到refreshBeanFactory,AbstractApplicationContext類中只抽象定義了refreshBeanFactory()方法,容器真正調用的是其子類AbstractRefreshableApplicationContext實現的 refreshBeanFactory()方法,方法的源碼如下:

    代碼1.3:AbstractRefreshableApplicationContext的refreshBeanFactory()方法

    1. protected final void refreshBeanFactory() throws BeansException {
    2. if (hasBeanFactory()) {//如果已經有容器,銷 毀 容器中的bean,關閉容器
    3. destroyBeans();
    4. closeBeanFactory();
    5. }
    6. try {
    7. //創建IoC容器
    8. DefaultListableBeanFactory beanFactory = createBeanFactory();
    9. //對IoC容器進行定製化,如設置啓動參數,開啓註解的自動裝配等
    10. beanFactory.setSerializationId(getId());
    11. customizeBeanFactory(beanFactory);
    12. //調用載入Bean定義的方法,主要這裏使用了一個委派模式,在當前類中只定義了抽象的loadBeanDefinitions方法,具體的實現調用子類容器
    13. loadBeanDefinitions(beanFactory);
    14. synchronized (this.beanFactoryMonitor) {
    15. this.beanFactory = beanFactory;
    16. }
    17. }
    18. catch (IOException ex) {
    19. throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    20. }
    21. }

    上面代碼註釋中已經說到了,具體的方法在子類中實現,我們先來看下到底有哪些子類,然後找到我們Web容器啓動走的路線:
    從上面的圖中可以看到,由於我們這裏是通過Web啓動的,所以我們自然來到了XmlWebApplicationContext的loadBeanDefinitions方法,來看下代碼:

    代碼1.3:XmlWebApplicationContext的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法

    1. @Override
    2. protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    3. // 這裏使用XMLBeanDefinitionReader來載入bean定義信息的XML文件(後面再講)
    4. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    5. //這裏配置reader的環境,其中ResourceLoader是我們用來定位bean定義信息資源位置的
    6. ///因爲上下文本身實現了ResourceLoader接口,所以可以直接把上下文作爲ResourceLoader傳遞給XmlBeanDefinitionReader
    7. beanDefinitionReader.setResourceLoader(this);
    8. beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    9. // Allow a subclass to provide custom initialization of the reader,
    10. // then proceed with actually loading the bean definitions.
    11. initBeanDefinitionReader(beanDefinitionReader);
    12. //這裏轉到定義好的XmlBeanDefinitionReader中對載入bean信息進行處理
    13. loadBeanDefinitions(beanDefinitionReader);
    14. }

    上面的XMLBeanDefinitionReader我們先不理睬,後面BeanDefinition解析的時候會講到這個類,我們直接看loadBeanDefinitions方法,這裏是具體載入bean信息,那麼要載入就要知道bean的定義文件在哪,所以Resource定位還要繼續跟下去,來看這個方法的源碼:

    代碼1.4:XmlWebApplicationContext的loadBeanDefinitions(XmlBeanDefinitionReader reader)方法

    1. protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    2. //獲取configLocations,也就是配置文件路徑
    3. String[] configLocations = getConfigLocations();
    4. if (configLocations != null) {
    5. for (String configLocation : configLocations) {
    6. reader.loadBeanDefinitions(configLocation);
    7. }
    8. }
    9. }

    看到這裏大家還記得之前我們的web.xml裏面配置的context-param嗎?還記得我們的ContextLoader裏面設置了wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM))嗎?這裏我們就取到了xml的位置。但是這裏我們並沒有看到我們一直在說的Resource,那麼繼續看代碼:經過了一次重載的方法,我們最終可以看到這個方法:

    代碼1.5:AbstractBeanDefinitionReader的loadBeanDefinitions方法

    1. public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
    2. //這裏得到當前定義的ResourceLoader,默認的使用DefaultResourceLoader
    3. ResourceLoader resourceLoader = getResourceLoader();
    4. if (resourceLoader == null) {
    5. throw new BeanDefinitionStoreException(
    6. "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
    7. }
    8. //下面這堆是對Resource的路徑模式進行解析(像我們在web.xml裏面有可能使用通配符等),得到需要的Resource集合,這些Resource集合指向了我們定義好的BeanDefinition的信息,可以是多個文件。
    9. if (resourceLoader instanceof ResourcePatternResolver) {
    10. //這裏處理我們在定義位置時使用的各種pattern,需要ResourcePatternResolver來完成
    11. try {
    12. Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
    13. int loadCount = loadBeanDefinitions(resources);
    14. if (actualResources != null) {
    15. for (Resource resource : resources) {
    16. actualResources.add(resource);
    17. }
    18. }
    19. if (logger.isDebugEnabled()) {
    20. logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
    21. }
    22. return loadCount;
    23. }
    24. catch (IOException ex) {
    25. throw new BeanDefinitionStoreException(
    26. "Could not resolve bean definition resource pattern [" + location + "]", ex);
    27. }
    28. }
    29. else {
    30. // 這裏通過ResourceLoader來完成位置定位
    31. Resource resource = resourceLoader.getResource(location);
    32. int loadCount = loadBeanDefinitions(resource);
    33. if (actualResources != null) {
    34. actualResources.add(resource);
    35. }
    36. if (logger.isDebugEnabled()) {
    37. logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
    38. }
    39. return loadCount;
    40. }
    41. }

    那麼對於取得Resource的具體過程,((ResourcePatternResolver) resourceLoader).getResources(location)這個方法大家點開看看就可以知道,是PathMatchingResourcePatternResolver中的實現的,它具體裏面就是針對我們配置的是否以“classpath*:” 開頭分別處理。對於resourceLoader.getResource(location)方法,具體是交給繼承Resource的子類完成的。這裏不再不多了,就放一張Resource的代碼路徑圖吧:

  • Resource組件

    Resource組件與ResourceLoader組件一起工作,將字符串格式指示的資源解析爲Resource對象。事實上ResourceLoader是Resource的工廠類, ResourceLoader的核心工作就是解析location。
    location示例:”classpath:applicationContext.xml”,”classpath:applicationContext-.xml”,”file:/some/resource/path/myTemplate.txt”,
  • http://XX/resource/path/myTemplate.txt“ ResourceLoader根據所指示的前綴返回特定的Resource對象。看下Resource的結構圖     Resource的結構圖
發佈了42 篇原創文章 · 獲贊 37 · 訪問量 26萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章