1. 程式人生 > >【Spring Framework 深入】—— IoC容器初始化 -> Bean定義資源的Resource定位

【Spring Framework 深入】—— IoC容器初始化 -> Bean定義資源的Resource定位

基本概念

ApplicationContext 繼承體系

本文主要關注ApplicationContext的繼承體系,至於BeanFactory的分支,以後再研究。
這裡寫圖片描述

BeanFactory or ApplicationContext?

BeanFactory和ApplicationContext都是實現IoC容器的基礎介面。Application是BeanFactory的子介面,包含了BeanFactory的功能,同時增加了對Transactions和AOP的支援。所以官方更推薦開發者使用ApplicationContext及其子類實現IoC容器。特別地,Spring在實現時,大量使用ApplicationContext實現BeanPostProcessor extension point。

官方文件有如下闡述:
The BeanFactory provides the underlying basis for Spring’s IoC functionality but it is only used directly in integration with other third-party frameworks and is now largely historical in nature for most users of Spring.
只有在與第三方框架整合時,才推薦使用BeanFactory。
BeanFactory vs ApplicationContext

org.springframework.context.Interface ApplicationContext

All Superinterfaces:(繼承介面)
ApplicationEventPublisher, BeanFactory, EnvironmentCapable, HierarchicalBeanFactory, ListableBeanFactory, MessageSource, ResourceLoader, ResourcePatternResolver
ApplicationContext實現了上述介面,豐富了基本IoC容器(BeanFactory)的行為,可以說是一個高階的IoC容器。
從ApplicationContext介面的實現,我們看出其特點:
1. 支援資訊源,可以實現國際化。(實現MessageSource介面)
2. 訪問資源。(實現ResourcePatternResolver介面)
3. 支援應用事件。(實現ApplicationEventPublisher介面)
它的常用具體類有ClasspathXmlApplicationContext和FileSystemXmlApplicationContext,Web 專案方面有XmlWebApplicationContext。

IoC容器的初始化

IoC容器的初始化主要包括BeanDefinition的Resource定位、載入解析和註冊這三個基本的過程。我們以ApplicationContext為例講解。由於篇幅過大,這篇文章先詳細講解Resource定位
這裡寫圖片描述

建立:

ApplicationContext =new FileSystemXmlApplicationContext(xmlPath);

建構函式:分3步,分別是呼叫父類建構函式、setConfigLocations和refresh

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)  
            throws BeansException {    
    super(parent);  
    setConfigLocations(configLocations);  
    if (refresh) {  
        refresh();  
    }  
} 

1.super(parent)的作用是為容器設定Bean資源載入器,通過debug,可知實際是由其父類AbstractApplicationContext 完成設定。注意,從這裡可以看到AbstractApplicationContext 繼承了DefaultResourceLoader,所以實際上它自身也作為資源載入器。

AbstractApplicationContext.java

public abstract class AbstractApplicationContext extends DefaultResourceLoader  
        implements ConfigurableApplicationContext, DisposableBean 

public AbstractApplicationContext(ApplicationContext parent) {
    this();
    setParent(parent);
}        
public AbstractApplicationContext() {
    this.resourcePatternResolver = getResourcePatternResolver();
}
protected ResourcePatternResolver getResourcePatternResolver() {
    return new PathMatchingResourcePatternResolver(this);
}

2.接下來,setConfigLocations(configLocations)的作用是設定Bean定義資原始檔的路徑,實際是由其父類AbstractRefreshableConfigApplicationContext完成設定

AbstractRefreshableConfigApplicationContext.java

public abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext
        implements BeanNameAware, InitializingBean

//從這裡可以看到配置Bean定義資原始檔可以使用兩種方式,字串和字串陣列
//字串會以,; /t/n這些分隔符分割
//location="a.xml,b.xml,..."
public void setConfigLocation(String location) {
        //String CONFIG_LOCATION_DELIMITERS = ",; /t/n";  
       //即多個資原始檔路徑之間用” ,; /t/n”分隔,解析成陣列形式 
        setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
}
//location=new String[]{“a.xml”,”b.xml”,……} 
public void setConfigLocations(String... locations) {
        if (locations != null) {
            Assert.noNullElements(locations, "Config locations must not be null");
            this.configLocations = new String[locations.length];
            for (int i = 0; i < locations.length; i++) {
                // resolvePath為同一個類中將字串解析為路徑的方法  
                this.configLocations[i] = resolvePath(locations[i]).trim();
            }
        }
        else {
            this.configLocations = null;
        }
}

3.接下來是開始進行Bean定義資原始檔載入,由AbstractApplicationContext的refresh函式完成。
refresh函式是一個模板方法,執行多個方法,而且提供了各(protected)方法的(預設)實現,其子類可以重寫它們
模板方法模式:在一個方法中定義一個演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類(使用protected方法)可以在不改變演算法結構的情況下,重新定義演算法中的某些步驟。
refresh函式中呼叫了多個方法,這裡先不詳細講解每一個方法,可以先通過英文註釋大概瞭解各方法的作用。

AbstractApplicationContext.java

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
            }
        }
    }

refresh()核心呼叫方法1:obtainFreshBeanFactory函式呼叫,完成了容器初始化的最重要最基礎的功能,Bean定義資源的Resource定位、載入解析和註冊。

AbstractApplicationContext.java

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
}
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

這裡使用了委派設計模式,obtainFreshBeanFactory中呼叫了兩個抽象方法,定義了obtainFreshBeanFactory的演算法骨架,實際的行為交給其子類(AbstractRefreshableApplicationContext)實現

AbstractRefreshableApplicationContext.java

    @Override
    protected final void refreshBeanFactory() throws BeansException {
        //如果已經有容器,銷燬容器中的bean,關閉容器,以保證在refresh之後使用的是新建立起來的IoC容器
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            //建立IoC容器
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            //對IoC容器進行定製化,如設定啟動引數,開啟註解的自動裝配等
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            //呼叫載入Bean定義的方法,這裡又使用了委派模式,在當前類中只定義了抽象的loadBeanDefinitions方法,具體的實現呼叫子類容器
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

在這個方法中,先判斷BeanFactory是否存在,如果存在則先銷燬beans並關閉beanFactory,接著建立DefaultListableBeanFactory,並呼叫loadBeanDefinitions(beanFactory)裝載bean
使用了委派模式,在當前類中只定義了抽象的loadBeanDefinitions方法,具體的實現呼叫子類容器(AbstractXmlApplicationContext)

AbstractXmlApplicationContext.java

@Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        //建立XmlBeanDefinitionReader,即建立Bean讀取器,並通過回撥設定到容器中去,容器使用該讀取器讀取Bean定義資源
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // Configure the bean definition reader with this context's
        // resource loading environment.
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        //為Bean讀取器設定資源載入器,  
        //AbstractXmlApplicationContext的祖先父類AbstractApplicationContext繼承DefaultResourceLoader,
        //因此,容器本身也是一個資源載入器
        //所以,這個資源載入器由始至終都是容器自身
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        initBeanDefinitionReader(beanDefinitionReader);
        loadBeanDefinitions(beanDefinitionReader);
    }

呼叫了另一個過載函式loadBeanDefinitions(beanDefinitionReader),委託給了XmlBeanDefinitionReader

AbstractXmlApplicationContext.java

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        //獲取Bean定義資源的定位
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        //如果configResources為空,則獲取FileSystemXmlApplicationContext構造方法中setConfigLocations方法設定的資源  
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            //XmlBeanDefinitionReader呼叫其父類AbstractBeanDefinitionReader讀取定位的Bean定義資源
            reader.loadBeanDefinitions(configLocations);
        }
    }

這裡也使用了委託模式,呼叫子類的獲取Bean定義資源定位的方法(getConfigResources()),該方法在ClassPathXmlApplicationContext中實現,FileSystemXmlApplicationContext預設返回null。

由於FileSystemXmlApplicationContext的getConfigResources返回null,因此程式執行configLocations分支,呼叫XmlBeanDefinitionReader的父類AbstractBeanDefinitionReader的loadBeanDefinitions(String… locations)方法

AbstractBeanDefinitionReader.java

@Override
    public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
        Assert.notNull(locations, "Location array must not be null");
        int counter = 0;
        for (String location : locations) {
            counter += loadBeanDefinitions(location);
        }
        return counter;
    }

對每一個location呼叫loadBeanDefinitions,其抽象父類AbstractBeanDefinitionReader定義了方法骨架

AbstractBeanDefinitionReader.java

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        //1.獲取在IoC容器初始化過程中設定的資源載入器,呼叫
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        }

        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                //2.將指定位置的Bean定義資原始檔解析為Spring IoC容器封裝的資源(Resource)
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                //3.委派呼叫其子類XmlBeanDefinitionReader的方法,載入Resource
                int loadCount = loadBeanDefinitions(resources);
                if (actualResources != null) {
                    for (Resource resource : resources) {
                        actualResources.add(resource);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
                }
                return loadCount;
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException(
                        "Could not resolve bean definition resource pattern [" + location + "]", ex);
            }
        }
        else {
            // Can only load single resources by absolute URL.
            //和上面步驟2一樣,獲得Resource。實際呼叫的是DefaultResourceLoader中的getSource()方法定位Resource
            Resource resource = resourceLoader.getResource(location);
            int loadCount = loadBeanDefinitions(resource);
            if (actualResources != null) {
                actualResources.add(resource);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
            }
            return loadCount;
        }
    }

FileSystemXmlApplicationContext本身就是DefaultResourceLoader的實現類
這裡寫圖片描述
意思是,AbstractBeanDefinitionReader中ResourceLoader resourceLoader = getResourceLoader(); 得到的是FileSystemXmlApplicationContext(AbstractApplicationContext)。
還記得在AbstractXmlApplicationContext中beanDefinitionReader.setResourceLoader(this); 為Bean讀取器設定的資源載入器,正是AbstractApplicationContext,因為繼承了DefaultResourceLoader,因此容器本身也是一個資源載入器

將指定位置的Bean定義資原始檔解析為IoC容器封裝的資源(Resource)的語句

Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
or
Resource resource = resourceLoader.getResource(location);

資源載入器獲取要讀入的資源(Resource)
實際上,呼叫了DefaultResourceLoader的getResource方法獲取Resource。

DefaultResourceLoader

@Override
    public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
        if (location.startsWith("/")) {
            return getResourceByPath(location);
        }
        //如果是類路徑的方式,那需要使用ClassPathResource 來得到bean 檔案的資源物件
        else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
        }
        else {
            try {
                // Try to parse the location as a URL...
                URL url = new URL(location);
                return new UrlResource(url);
            }
            catch (MalformedURLException ex) {
                // No URL -> resolve as resource path.
                return getResourceByPath(location);
            }
        }
    }

至此,完成了Bean定義資源的Resource定位

總結一下從建立容器之後各個父類方法呼叫,不然就有點懵逼了!
ApplicationContext
這裡寫圖片描述

接下來,開始Bean定義資源(已封裝成Resource)的載入解析

回到XmlBeanDefinitionReader的loadBeanDefinitions方法

XmlBeanDefinitionReader.java

@Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        //將讀入的XML資源進行特殊編碼處理
        return loadBeanDefinitions(new EncodedResource(resource));
    }
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }

        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<EncodedResource>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

相關推薦

Spring Framework 深入—— IoC容器初始 -> Bean定義資源Resource定位

基本概念 ApplicationContext 繼承體系 本文主要關注ApplicationContext的繼承體系,至於BeanFactory的分支,以後再研究。 BeanFactory or ApplicationContext? Bea

spring原始碼分析IOC容器初始(二)

前言:在【spring原始碼分析】IOC容器初始化(一)中已經分析了匯入bean階段,本篇接著分析bean解析階段。 1.解析bean程式呼叫鏈 同樣,先給出解析bean的程式呼叫鏈: 根據程式呼叫鏈,整理出在解析bean過程中主要涉及的類和相關方法。 2.解析bean原始碼分

Spring- IOC容器初始過程

浪費了“黃金五年”的Java程式設計師,還有救嗎? >>>   

Ioc容器初始-bean資源定位(2)

public void setConfigLocation(String location ) { setConfigLocations(StringUtils. tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS ));

Spring原始碼解析—— 結合SpringMVC過程理解IOC容器初始之註解部分探究

前面的文章寫了xml中直接配置bean進行IOC的過程解析,接下來會針對註解進行IOC容器初始化的過程解析 因為會與之前的內容存在部分重疊,因此會針對相同的部分簡略帶過,針對不同的部分做重點說明:   一、Xml的配置和程式碼中的註解配置: applicationContext.xml配置新

Spring原始碼解析--《SPRING技術內幕:深入解析Spring架構與設計原理》讀書筆記(一):IOC容器初始過程

通過閱讀相關章節內容,Spring中IOC容器的載入中,我們需要了解下列幾個概念: Resource:是一個定位、訪問資源的抽象介面,包含了多種資源操作的基礎方法定義,如getInputStream()、exists()、isOpen()、getD

Spring原始碼閱讀IOC核心容器基礎和繼承體系結構

BeanFactory BeanFacotry作為Spring的根容器物件,提供了對Bean的基礎操作功能,包括例項化、配置、管理Bean等。 ApplicationContext ApplicationContext對BeanFactory進行了進一步地封裝,內建了Bean

Spring源碼分析總結(一)-IOC容器初始

Spring源碼分析總結一、IOC容器的初始化過程 IOC容器的初始化是由refresh()方法啟動。經常使用的ApplicationContext 有:ClassPathXmlApplicationContext和FileSystemXmlApplicationContext、XmlWebApp

03.Spring IoC 容器 - 初始

itl ret num servlet fontsize eat 圖片 number sources 基本概念 Spring IoC 容器的初始化過程在監聽器 ContextLoaderListener 類中定義。 具體由該類的的 configureAndRefreshWe

spring原始碼學習之路---IOC容器初始要義之bean定義載入(四)

上章說到要帶各位去看看bean定義載入的要義,其實就是loadBeanDefinitions這個方法的具體實現步驟,下面我們跟隨這個方法去看下它到底是如何載入bean定義的。 上面是我擷取的實現了loadBeanDefinitions的類級別截圖,loadBeanDefinit

spring原始碼學習之路---深度分析IOC容器初始過程(三)

分析FileSystemXmlApplicationContext的建構函式,到底都做了什麼,導致IOC容器初始化成功。 public FileSystemXmlApplicationContext(String[] configLocations, boolean ref

spring學習-day2IOC-DI-scope-setter和構造器注入

【補充】 這是早就寫了的文章,如今有新的理解,想補充完成。 1.scope 2.IOC 3.DI 4.setter注入和構造器注入 5.init和destory 【打頭說明】 IOC說的是控制反轉,意思就是讓spring來建立物件,竟然讓spring來建立物件

Spring原始碼解析-4、IOC容器初始

IOC容器初始化的幾個步驟 IOC容器的初始化由以下三個步驟構成: 1、BeanDefinition的Resource定位 由ResourceLoader通過統一的Resource介面來完成,Resource對不同形式的BeanDefinition有統一的介面。 比如檔案系統中的Bean

SPRING原理解析-Ioc容器初始

       IoC容器的初始化就是含有BeanDefinition資訊的Resource的定位、載入、解析、註冊四個過程,最終我們配置的bean,以beanDefinition的資料結構存在於IoC容器即記憶體中。這裡並不涉及bean的依賴注入,只是bean定義的載入。但

spring技術內幕筆記:IoC容器初始過程(2)- BeanDefinition的載入

Spring版本:4.3.8.RELEASEBeanDefinition載入過程,相當於把定義的BeanDefinition在IoC容器中轉換成一個Spring內部表示的資料結構的過程。IoC容器對Bean的管理和依賴注入功能的實現,就是通過對其持有的BeanDefiniti

Spring技術內幕之IOC容器的實現(01)-IOC容器初始過程

Spring IOC容器的初始化過程 Spring IOC容器的初始化過程主要包括BeanDefinition的Resouce定位/載入/註冊三個基本過程。Spring把這三個過程的實現分別放在不同的模組下,通過這樣的設計方式可以使使用者更加靈活地對這個三個過程進行裁剪和自

Spring原始碼解讀-Spring IoC容器初始資源定位

**IoC初始化過程 首先spring IoC容器的初始化,要分成三大部分,BeanDefinition的 Resource定位、載入和註冊三個基本過程。 今天我要說的就是資原始檔的定位,IoC容

spring 原始碼分析--IOC容器初始

 上一節將xml文件解析為DOM ,並且建立了一個 BeanDefinitionParserDelegate 型別的物件,在這一節,將使用這個物件來完成對bean的裝載工作。 2.1.1.1 parseBeanDefinitions (root, delegate): 該方法體完成註冊過程。

spring容器初始bean之後或銷燬bean之前,能做的操作

通過 <bean> 標籤 init-method  初始化bean之後呼叫的方法 destroy-method  銷燬bean之前呼叫的操作方法 <bean id="initQuartzJob" class="com.upinc

IOC容器初始(原始碼解讀)

一、過程(資源定位,Bean的載入,解析,以及註冊) 第一個過程是Resource資源定位。這個Resouce指的是BeanDefinition的資源定位。這個過程就是容器找資料的過程,就像水桶裝水需要先找到水一樣 第二個過程是BeanDefinition的