Spring框架系列(7) - Spring IOC實現原理詳解之IOC初始化流程

語言: CN / TW / HK

上文,我們看了IOC設計要點和設計結構;緊接著這篇,我們可以看下原始碼的實現了:Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的。@pdai

引入

上文,我們看了IOC設計要點和設計結構;�緊接著這篇,我們可以看下原始碼的實現了:Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的(就是我們圈出來的部分)

如何將Bean從XML配置中解析後放到IoC容器中的?

本文的目標就是分析Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的。

初始化的入口

對於xml配置的Spring應用,在main()方法中例項化ClasspathXmlApplicationContext即可建立一個IoC容器。我們可以從這個構造方法開始,探究一下IoC容器的初始化過程。

java // create and configure beans ApplicationContext context = new ClassPathXmlApplicationContext("aspects.xml", "daos.xml", "services.xml");

```java public ClassPathXmlApplicationContext(String... configLocations) throws BeansException { this(configLocations, true, (ApplicationContext)null); }

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { // 設定Bean資源載入器 super(parent);

// 設定配置路徑
this.setConfigLocations(configLocations);

// 初始化容器
if (refresh) {
    this.refresh();
}

} ```

設定資源解析器和環境

呼叫父類容器AbstractApplicationContext的構造方法(super(parent)方法)為容器設定好Bean資源載入器

```java public AbstractApplicationContext(@Nullable ApplicationContext parent) { // 預設建構函式初始化容器id, name, 狀態 以及 資源解析器 this();

// 將父容器的Environment合併到當前容器
this.setParent(parent);

} ```

通過AbstractApplicationContext預設建構函式初始化容器id, name, 狀態 以及 資源解析器

java public AbstractApplicationContext() { this.logger = LogFactory.getLog(this.getClass()); this.id = ObjectUtils.identityToString(this); this.displayName = ObjectUtils.identityToString(this); this.beanFactoryPostProcessors = new ArrayList(); this.active = new AtomicBoolean(); this.closed = new AtomicBoolean(); this.startupShutdownMonitor = new Object(); this.applicationStartup = ApplicationStartup.DEFAULT; this.applicationListeners = new LinkedHashSet(); this.resourcePatternResolver = this.getResourcePatternResolver(); } // Spring資源載入器 protected ResourcePatternResolver getResourcePatternResolver() { return new PathMatchingResourcePatternResolver(this); }

通過AbstractApplicationContext的setParent(parent)方法將父容器的Environment合併到當前容器

java public void setParent(@Nullable ApplicationContext parent) { this.parent = parent; if (parent != null) { Environment parentEnvironment = parent.getEnvironment(); if (parentEnvironment instanceof ConfigurableEnvironment) { this.getEnvironment().merge((ConfigurableEnvironment)parentEnvironment); } } }

設定配置路徑

在設定容器的資源載入器之後,接下來FileSystemXmlApplicationContet執行setConfigLocations方法通過呼叫其父類AbstractRefreshableConfigApplicationContext的方法進行對Bean定義資原始檔的定位

```java public void setConfigLocations(@Nullable 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) {
        // 解析配置路徑
        this.configLocations[i] = this.resolvePath(locations[i]).trim();
    }
} else {
    this.configLocations = null;
}

} protected String resolvePath(String path) { // 從上一步Environment中解析 return this.getEnvironment().resolveRequiredPlaceholders(path); } ```

初始化的主體流程

Spring IoC容器對Bean定義資源的載入是從refresh()函式開始的,refresh()是一個模板方法,refresh()方法的作用是:在建立IoC容器前,如果已經有容器存在,則需要把已有的容器銷燬和關閉,以保證在refresh之後使用的是新建立起來的IoC容器。refresh的作用類似於對IoC容器的重啟,在新建立好的容器中對容器進行初始化,對Bean定義資源進行載入。

```java @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

    // 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);

        StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
        // Invoke factory processors registered as beans in the context.
        invokeBeanFactoryPostProcessors(beanFactory);

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

        // 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();
        contextRefresh.end();
    }
}

} ```

這裡的設計上是一個非常典型的資源類載入處理型的思路,頭腦中需要形成如下圖的頂層思路(而不是隻停留在流水式的方法上面):

  • 模板方法設計模式,模板方法中使用典型的鉤子方法
  • 具體的初始化載入方法插入到鉤子方法之間
  • 將初始化的階段封裝,用來記錄當前初始化到什麼階段;常見的設計是xxxPhase/xxxStage;
  • 資源載入初始化有失敗等處理,必然是try/catch/finally...

初始化BeanFactory之obtainFreshBeanFactory

AbstractApplicationContext的obtainFreshBeanFactory()方法呼叫子類容器的refreshBeanFactory()方法,啟動容器載入Bean定義資原始檔的過程,程式碼如下:

java protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { // 這裡使用了委派設計模式,父類定義了抽象的refreshBeanFactory()方法,具體實現呼叫子類容器的refreshBeanFactory()方法 refreshBeanFactory(); return getBeanFactory(); }

AbstractApplicationContext類中只抽象定義了refreshBeanFactory()方法,容器真正呼叫的是其子類AbstractRefreshableApplicationContext實現的refreshBeanFactory()方法; 在建立IoC容器前,如果已經有容器存在,則需要把已有的容器銷燬和關閉,以保證在refresh之後使用的是新建立起來的IoC容器。方法的原始碼如下:

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

初始化BeanFactory之loadBeanDefinitions

AbstractRefreshableApplicationContext中只定義了抽象的loadBeanDefinitions方法,容器真正呼叫的是其子類AbstractXmlApplicationContext對該方法的實現,AbstractXmlApplicationContext的主要原始碼如下:

```java protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // 建立XmlBeanDefinitionReader,即建立Bean讀取器,並通過回撥設定到容器中去,容器使用該讀取器讀取Bean定義資源
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// 配置上下文的環境,資源載入器、解析器
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // 為Bean讀取器設定SAX xml解析器

// 允許子類自行初始化(比如校驗機制),並提供真正的載入方法
initBeanDefinitionReader(beanDefinitionReader); // 當Bean讀取器讀取Bean定義的Xml資原始檔時,啟用Xml的校驗機制  
loadBeanDefinitions(beanDefinitionReader);

}

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { // 載入XML配置方式裡的Bean定義的資源 Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } // 載入建構函式裡配置的Bean配置檔案,即{"aspects.xml", "daos.xml", "services.xml"} String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } } ```

Xml Bean讀取器(XmlBeanDefinitionReader)呼叫其父類AbstractBeanDefinitionReader的 reader.loadBeanDefinitions方法讀取Bean定義資源。

由於我們使用ClassPathXmlApplicationContext作為例子分析,因此getConfigResources的返回值為null,因此程式執行reader.loadBeanDefinitions(configLocations)分支。

AbstractBeanDefinitionReader讀取Bean定義資源

AbstractBeanDefinitionReader的loadBeanDefinitions方法原始碼如下:

```java @Override public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null); }

public int loadBeanDefinitions(String location, @Nullable Set actualResources) throws BeanDefinitionStoreException { ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available"); }

// 模式匹配型別的解析器,這種方式是載入多個滿足匹配條件的資源
if (resourceLoader instanceof ResourcePatternResolver) {
    try {
        // 獲取到要載入的資源
        Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
        int count = loadBeanDefinitions(resources); // 委派呼叫其子類XmlBeanDefinitionReader的方法,實現載入功能  
        if (actualResources != null) {
            Collections.addAll(actualResources, resources);
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
        }
        return count;
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException(
                "Could not resolve bean definition resource pattern [" + location + "]", ex);
    }
}
else {
    // 只能通過絕對路徑URL載入單個資源.
    Resource resource = resourceLoader.getResource(location);
    int count = loadBeanDefinitions(resource);
    if (actualResources != null) {
        actualResources.add(resource);
    }
    if (logger.isTraceEnabled()) {
        logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
    }
    return count;
}

}

```

從對AbstractBeanDefinitionReader的loadBeanDefinitions方法原始碼分析可以看出該方法做了以下兩件事:

  • 首先,呼叫資源載入器的獲取資源方法resourceLoader.getResource(location),獲取到要載入的資源。
  • 其次,真正執行載入功能是其子類XmlBeanDefinitionReader的loadBeanDefinitions方法。

XmlBeanDefinitionReader載入Bean定義資源

繼續看子類XmlBeanDefinitionReader的loadBeanDefinitions(Resource …)方法看到代表bean檔案的資源定義以後的載入過程。

```java /* * 本質上是載入XML配置的Bean。 * @param inputSource the SAX InputSource to read from * @param resource the resource descriptor for the XML file / protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {

try {
    Document doc = doLoadDocument(inputSource, resource); // 將Bean定義資源轉換成Document物件
    int count = registerBeanDefinitions(doc, resource);
    if (logger.isDebugEnabled()) {
        logger.debug("Loaded " + count + " bean definitions from " + resource);
    }
    return count;
}
catch (BeanDefinitionStoreException ex) {
    throw ex;
}
catch (SAXParseException ex) {
    throw new XmlBeanDefinitionStoreException(resource.getDescription(),
            "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
    throw new XmlBeanDefinitionStoreException(resource.getDescription(),
            "XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
    throw new BeanDefinitionStoreException(resource.getDescription(),
            "Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
    throw new BeanDefinitionStoreException(resource.getDescription(),
            "IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
    throw new BeanDefinitionStoreException(resource.getDescription(),
            "Unexpected exception parsing XML document from " + resource, ex);
}

}

// 使用配置的DocumentLoader載入XML定義檔案為Document. protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); } ```

通過原始碼分析,載入Bean定義資原始檔的最後一步是將Bean定義資源轉換為Document物件,該過程由documentLoader實現

DocumentLoader將Bean定義資源轉換為Document物件

DocumentLoader將Bean定義資源轉換成Document物件的原始碼如下:

```java // 使用標準的JAXP將載入的Bean定義資源轉換成document物件 @Override public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

// 建立檔案解析器工廠
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
    logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
// 建立文件解析器
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource); // 解析

}

protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware) throws ParserConfigurationException {

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(namespaceAware);

// 設定解析XML的校驗
if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
    factory.setValidating(true);
    if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
        // Enforce namespace aware for XSD...
        factory.setNamespaceAware(true);
        try {
            factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
        }
        catch (IllegalArgumentException ex) {
            ParserConfigurationException pcex = new ParserConfigurationException(
                    "Unable to validate using XSD: Your JAXP provider [" + factory +
                    "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
                    "Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
            pcex.initCause(ex);
            throw pcex;
        }
    }
}

return factory;

} ```

該解析過程呼叫JavaEE標準的JAXP標準進行處理。

至此Spring IoC容器根據定位的Bean定義資原始檔,將其載入讀入並轉換成為Document物件過程完成。

接下來我們要繼續分析Spring IoC容器將載入的Bean定義資原始檔轉換為Document物件之後,是如何將其解析為Spring IoC管理的Bean物件並將其註冊到容器中的。

XmlBeanDefinitionReader解析載入的Bean定義資原始檔

XmlBeanDefinitionReader類中的doLoadBeanDefinitions方法是從特定XML檔案中實際載入Bean定義資源的方法,該方法在載入Bean定義資源之後將其轉換為Document物件,接下來呼叫registerBeanDefinitions啟動Spring IoC容器對Bean定義的解析過程,registerBeanDefinitions方法原始碼如下:

```java // 按照Spring的Bean語義要求將Bean定義資源解析並轉換為容器內部資料結構 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); // 解析過程入口,這裡使用了委派模式,具體的解析實現過程有實現類DefaultBeanDefinitionDocumentReader完成
documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; // 返回此次解析了多少個物件 }

// 建立BeanDefinitionDocumentReader物件,解析Document物件
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() { return BeanUtils.instantiateClass(this.documentReaderClass); }

/* * Create the {@link XmlReaderContext} to pass over to the document reader. / public XmlReaderContext createReaderContext(Resource resource) { return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, getNamespaceHandlerResolver()); } ```

Bean定義資源的載入解析分為以下兩個過程:

  • 首先,通過呼叫XML解析器將Bean定義資原始檔轉換得到Document物件,但是這些Document物件並沒有按照Spring的Bean規則進行解析。這一步是載入的過程
  • 其次,在完成通用的XML解析之後,按照Spring的Bean規則對Document物件進行解析。

按照Spring的Bean規則對Document物件解析的過程是在介面BeanDefinitionDocumentReader的實現類DefaultBeanDefinitionDocumentReader中實現的。

DefaultBeanDefinitionDocumentReader對Bean定義的Document物件解析

BeanDefinitionDocumentReader介面通過registerBeanDefinitions方法呼叫其實現類DefaultBeanDefinitionDocumentReader對Document物件進行解析,解析的程式碼如下:

```java @Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; doRegisterBeanDefinitions(doc.getDocumentElement()); }

// 註冊配置的Beans @SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...) protected void doRegisterBeanDefinitions(Element root) { // Any nested elements will cause recursion in this method. In // order to propagate and preserve default-* attributes correctly, // keep track of the current (parent) delegate, which may be null. Create // the new (child) delegate with a reference to the parent for fallback purposes, // then ultimately reset this.delegate back to its original (parent) reference. // this behavior emulates a stack of delegates without actually necessitating one. BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent);

if (this.delegate.isDefaultNamespace(root)) {
    String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
    if (StringUtils.hasText(profileSpec)) {
        String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        // We cannot use Profiles.of(...) since profile expressions are not supported
        // in XML config. See SPR-12458 for details.
        if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                        "] not matching: " + getReaderContext().getResource());
            }
            return;
        }
    }
}

preProcessXml(root);
parseBeanDefinitions(root, this.delegate); // 從Document的根元素開始進行Bean定義的Document物件  
postProcessXml(root);

this.delegate = parent;

} ```

BeanDefinitionParserDelegate解析Bean定義資原始檔生成BeanDefinition

```java /* * Parse the elements at the root level in the document: * "import", "alias", "bean". * @param root the DOM root element of the document / protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {

// 如果元素節點是<Import>匯入元素,進行匯入解析
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
    importBeanDefinitionResource(ele);
}
// 如果元素節點是<Alias>別名元素,進行別名解析 
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
    processAliasRegistration(ele);
}
// 如果元素節點<Bean>元素, 按照Spring的Bean規則解析元素  
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
    processBeanDefinition(ele, delegate);
}
// 如果元素節點<Beans>元素,即它是巢狀型別的
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
    // 遞迴解析
    doRegisterBeanDefinitions(ele);
}

} ```

解析Bean生成BeanDefinitionHolder的方法

java /** * Process the given bean element, parsing the bean definition * and registering it with the registry. */ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { // 註冊最終的裝飾例項 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); } catch (BeanDefinitionStoreException ex) { getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, ex); } // Send registration event. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } } (這裡就不展開了,無非就是解析XML各種元素,來生成BeanDefinition)

解析過後的BeanDefinition在IoC容器中的註冊

Document物件的解析後得到封裝BeanDefinition的BeanDefinitionHold物件,然後呼叫BeanDefinitionReaderUtils的registerBeanDefinition方法向IoC容器註冊解析的Bean,BeanDefinitionReaderUtils的註冊的原始碼如下:

```java // 通過BeanDefinitionRegistry將BeanDefinitionHolder註冊到BeanFactory public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {

// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
    for (String alias : aliases) {
        registry.registerAlias(beanName, alias);
    }
}

} ```

當呼叫BeanDefinitionReaderUtils向IoC容器註冊解析的BeanDefinition時,真正完成註冊功能的是DefaultListableBeanFactory。

DefaultListableBeanFactory向IoC容器註冊解析後的BeanDefinition

IOC容器本質上就是一個beanDefinitionMap, 註冊即將BeanDefinition put到map中

```java /* Map of bean definition objects, keyed by bean name. / private final Map beanDefinitionMap = new ConcurrentHashMap<>(256);

/* Map from bean name to merged BeanDefinitionHolder. / private final Map mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256);

@Override public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {

Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");

if (beanDefinition instanceof AbstractBeanDefinition) {
    try {
        ((AbstractBeanDefinition) beanDefinition).validate();
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                "Validation of bean definition failed", ex);
    }
}

BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
// 如果已經註冊
if (existingDefinition != null) {
    // 檢查是否可以覆蓋
    if (!isAllowBeanDefinitionOverriding()) {
        throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
    }
    else if (existingDefinition.getRole() < beanDefinition.getRole()) {
        // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
        if (logger.isInfoEnabled()) {
            logger.info("Overriding user-defined bean definition for bean '" + beanName +
                    "' with a framework-generated bean definition: replacing [" +
                    existingDefinition + "] with [" + beanDefinition + "]");
        }
    }
    else if (!beanDefinition.equals(existingDefinition)) {
        if (logger.isDebugEnabled()) {
            logger.debug("Overriding bean definition for bean '" + beanName +
                    "' with a different definition: replacing [" + existingDefinition +
                    "] with [" + beanDefinition + "]");
        }
    }
    else {
        if (logger.isTraceEnabled()) {
            logger.trace("Overriding bean definition for bean '" + beanName +
                    "' with an equivalent definition: replacing [" + existingDefinition +
                    "] with [" + beanDefinition + "]");
        }
    }
    // 覆蓋
    this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
    if (hasBeanCreationStarted()) {
        // Cannot modify startup-time collection elements anymore (for stable iteration)
        synchronized (this.beanDefinitionMap) {
            this.beanDefinitionMap.put(beanName, beanDefinition);
            List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
            updatedDefinitions.addAll(this.beanDefinitionNames);
            updatedDefinitions.add(beanName);
            this.beanDefinitionNames = updatedDefinitions;
            removeManualSingletonName(beanName);
        }
    }
    else {
        // Still in startup registration phase
        this.beanDefinitionMap.put(beanName, beanDefinition);
        this.beanDefinitionNames.add(beanName);
        removeManualSingletonName(beanName);
    }
    //重置所有已經註冊過的BeanDefinition的快取  
    this.frozenBeanDefinitionNames = null;
}

if (existingDefinition != null || containsSingleton(beanName)) {
    resetBeanDefinition(beanName);
}
else if (isConfigurationFrozen()) {
    clearByTypeCache();
}

} ```

至此,Bean定義資原始檔中配置的Bean被解析過後,已經註冊到IoC容器中,被容器管理起來,真正完成了IoC容器初始化所做的全部工作。現 在IoC容器中已經建立了整個Bean的配置資訊,這些BeanDefinition資訊已經可以使用,並且可以被檢索,IoC容器的作用就是對這些註冊的Bean定義資訊進行處理和維護。這些的註冊的Bean定義資訊是IoC容器控制反轉的基礎,正是有了這些註冊的資料,容器才可以進行依賴注入。

總結

現在通過上面的程式碼,總結一下IOC容器初始化的基本步驟:

  • 初始化的入口在容器實現中的 refresh()呼叫來完成

  • 對 bean 定義載入 IOC 容器使用的方法是 loadBeanDefinition,其中的大致過程如下:

  • 通過 ResourceLoader 來完成資原始檔位置的定位,DefaultResourceLoader 是預設的實現,同時上下文字身就給出了 ResourceLoader 的實現,可以從類路徑,檔案系統, URL 等方式來定為資源位置。如果是 XmlBeanFactory作為 IOC 容器,那麼需要為它指定 bean 定義的資源,也就是說 bean 定義檔案時通過抽象成 Resource 來被 IOC 容器處理的
  • 通過 BeanDefinitionReader來完成定義資訊的解析和 Bean 資訊的註冊, 往往使用的是XmlBeanDefinitionReader 來解析 bean 的 xml 定義檔案 - 實際的處理過程是委託給 BeanDefinitionParserDelegate 來完成的,從而得到 bean 的定義資訊,這些資訊在 Spring 中使用 BeanDefinition 物件來表示 - 這個名字可以讓我們想到loadBeanDefinition,RegisterBeanDefinition 這些相關的方法 - 他們都是為處理 BeanDefinitin 服務的
  • 容器解析得到 BeanDefinition 以後,需要把它在 IOC 容器中註冊,這由 IOC 實現 BeanDefinitionRegistry 介面來實現。註冊過程就是在 IOC 容器內部維護的一個HashMap 來儲存得到的 BeanDefinition 的過程。這個 HashMap 是 IoC 容器持有 bean 資訊的場所,以後對 bean 的操作都是圍繞這個HashMap 來實現的.

  • 然後我們就可以通過 BeanFactory 和 ApplicationContext 來享受到 Spring IOC 的服務了,在使用 IOC 容器的時候,我們注意到除了少量粘合程式碼,絕大多數以正確 IoC 風格編寫的應用程式程式碼完全不用關心如何到達工廠,因為容器將把這些物件與容器管理的其他物件鉤在一起。基本的策略是把工廠放到已知的地方,最好是放在對預期使用的上下文有意義的地方,以及程式碼將實際需要訪問工廠的地方。 Spring 本身提供了對宣告式載入 web 應用程式用法的應用程式上下文,並將其儲存在ServletContext 中的框架實現。

參考文章

https://blog.csdn.net/qq_36212439/article/details/82749963

https://juejin.cn/post/6973884466171215908

https://juejin.cn/post/6844903838743265294

https://blog.csdn.net/hjing123/article/details/104867343

https://www.cnblogs.com/wl20200316/p/12522993.html

更多文章

首先, 從Spring框架的整體架構和組成對整體框架有個認知。

  • Spring基礎 - Spring和Spring框架組成
  • Spring是什麼?它是怎麼誕生的?有哪些主要的元件和核心功能呢? 本文通過這幾個問題幫助你構築Spring和Spring Framework的整體認知。

其次,通過案例引出Spring的核心(IoC和AOP),同時對IoC和AOP進行案例使用分析。

基於Spring框架和IOC,AOP的基礎,為構建上層web應用,需要進一步學習SpringMVC。

  • Spring基礎 - SpringMVC請求流程和案例
  • 前文我們介紹了Spring框架和Spring框架中最為重要的兩個技術點(IOC和AOP),那我們如何更好的構建上層的應用呢(比如web 應用),這便是SpringMVC;Spring MVC是Spring在Spring Container Core和AOP等技術基礎上,遵循上述Web MVC的規範推出的web開發框架,目的是為了簡化Java棧的web開發。 本文主要介紹SpringMVC的請求流程和基礎案例的編寫和執行。

Spring進階 - IoC,AOP以及SpringMVC的原始碼分析

  • Spring進階 - Spring IOC實現原理詳解之IOC體系結構設計
  • 在對IoC有了初步的認知後,我們開始對IOC的實現原理進行深入理解。本文將幫助你站在設計者的角度去看IOC最頂層的結構設計
  • Spring進階 - Spring IOC實現原理詳解之IOC初始化流程
  • 上文,我們看了IOC設計要點和設計結構;緊接著這篇,我們可以看下原始碼的實現了:Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的
  • Spring進階 - Spring IOC實現原理詳解之Bean例項化(生命週期,迴圈依賴等)
  • 上文,我們看了IOC設計要點和設計結構;以及Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的;容器中存放的是Bean的定義即BeanDefinition放到beanDefinitionMap中,本質上是一個ConcurrentHashMap<String, Object>;並且BeanDefinition介面中包含了這個類的Class資訊以及是否是單例等。那麼如何從BeanDefinition中例項化Bean物件呢,這是本文主要研究的內容?
  • Spring進階 - Spring AOP實現原理詳解之切面實現
  • 前文,我們分析了Spring IOC的初始化過程和Bean的生命週期等,而Spring AOP也是基於IOC的Bean載入來實現的。本文主要介紹Spring AOP原理解析的切面實現過程(將切面類的所有切面方法根據使用的註解生成對應Advice,並將Advice連同切入點匹配器和切面類等資訊一併封裝到Advisor,為後續交給代理增強實現做準備的過程)。
  • Spring進階 - Spring AOP實現原理詳解之AOP代理
  • 上文我們介紹了Spring AOP原理解析的切面實現過程(將切面類的所有切面方法根據使用的註解生成對應Advice,並將Advice連同切入點匹配器和切面類等資訊一併封裝到Advisor)。本文在此基礎上繼續介紹,代理(cglib代理和JDK代理)的實現過程。
  • Spring進階 - Spring AOP實現原理詳解之Cglib代理實現
  • 我們在前文中已經介紹了SpringAOP的切面實現和建立動態代理的過程,那麼動態代理是如何工作的呢?本文主要介紹Cglib動態代理的案例和SpringAOP實現的原理。
  • Spring進階 - Spring AOP實現原理詳解之JDK代理實現
  • 上文我們學習了SpringAOP Cglib動態代理的實現,本文主要是SpringAOP JDK動態代理的案例和實現部分。
  • Spring進階 - SpringMVC實現原理之DispatcherServlet初始化的過程
  • 前文我們有了IOC的原始碼基礎以及SpringMVC的基礎,我們便可以進一步深入理解SpringMVC主要實現原理,包含DispatcherServlet的初始化過程和DispatcherServlet處理請求的過程的原始碼解析。本文是第一篇:DispatcherServlet的初始化過程的原始碼解析。
  • Spring進階 - SpringMVC實現原理之DispatcherServlet處理請求的過程
  • 前文我們有了IOC的原始碼基礎以及SpringMVC的基礎,我們便可以進一步深入理解SpringMVC主要實現原理,包含DispatcherServlet的初始化過程和DispatcherServlet處理請求的過程的原始碼解析。本文是第二篇:DispatcherServlet處理請求的過程的原始碼解析。