Spring 加載多個xml配置文件的原理分析

示例

先給出兩個Bean的配置文件:

spring-configlication.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="person" class="com.john.aop.Person">
    </bean>
    <bean id="ChineseFemaleSinger" class="com.john.beanFactory.Singer" abstract="true" >
        <property name="country" value="中國"/>
        <property name="gender" value="女"/>
    </bean>

</beans>

spring-config-instance-factory.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="carFactory" class="com.john.domain.CarFactory" />
    <!--實例工廠方法創建bean-->
    <bean id="instanceCar" factory-bean="carFactory" factory-method="createCar">
        <constructor-arg ref="brand"/>
    </bean>
    <bean id="brand" class="com.john.domain.Brand" />
</beans>

java示例代碼

public class ConfigLocationsDemo {

    public static void main(String[] args) {

        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
        applicationContext.setConfigLocations("spring-configlocation.xml","spring-config-instance-factory.xml");
        applicationContext.refresh();
         String[] beanNames = applicationContext.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }
    }
}

這樣我們就會在控制臺打印出兩個配置文件中所有的Bean.

person
ChineseFemaleSinger
carFactory
instanceCar
brand

Process finished with exit code 0

實現

AbstractRefreshableConfigApplicationContext

從類的名字推導出這是一個帶刷新功能並且帶配置功能的應用上下文。

/**
     * Set the config locations for this application context.
     * <p>If not set, the implementation may use a default as appropriate.
     */
    public void setConfigLocations(@Nullable String... locations) {
        if (locations != null) {

            this.configLocations = new String[locations.length];
            for (int i = 0; i < locations.length; i++) {
                this.configLocations[i] = resolvePath(locations[i]).trim();
            }
        }
        else {
            this.configLocations = null;
        }
    }

這個方法很好理解,首先根據傳入配置文件路徑字符串數組遍歷,並且裡面調用瞭resolvePath方法解析占位符。那麼我們要想下瞭,這裡隻做瞭解析占位符並且把字符串賦值給configLocations變量,那必然肯定會在什麼時候去讀取這個路徑下的文件並加載bean吧?會不會是在應用上下文調用refresh方法的時候去加載呢?帶著思考我們來到瞭應用上下文的refresh方法。

AbstractApplicationContext

     @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {

            // Tell the subclass to refresh the internal bean factory.
            //告訴子類刷新 內部BeanFactory
            //https://www.iteye.com/blog/rkdu2-163-com-2003638
            //內部會加載bean定義
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      }
    }

    /**
     * Tell the subclass to refresh the internal bean factory.
     * @return the fresh BeanFactory instance
     * @see #refreshBeanFactory()
     * @see #getBeanFactory()
     */
    //得到刷新過的beanFactory
    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        //抽象類AbstractRefreshableApplicationContext
        //裡面會加載bean定義
        //todo 加載bean定義
        refreshBeanFactory();
        //如果beanFactory為null 會報錯
        return getBeanFactory();
    }

    /**
     * Subclasses must implement this method to perform the actual configuration load.
     * The method is invoked by {@link #refresh()} before any other initialization work.
     * <p>A subclass will either create a new bean factory and hold a reference to it,
     * or return a single BeanFactory instance that it holds. In the latter case, it will
     * usually throw an IllegalStateException if refreshing the context more than once.
     * @throws BeansException if initialization of the bean factory failed
     * @throws IllegalStateException if already initialized and multiple refresh
     * attempts are not supported
     */
    //AbstractRefreshableApplicationContext 實現瞭此方法
    //GenericApplicationContext 實現瞭此方法
    protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;

我們看到refreshBeanFactory的第一句註釋就提到瞭Subclasses must implement this method to perform the actual configuration load,意思就是子類必須實現此方法來完成最終的配置加載,那實現此方法的Spring內部默認有兩個類,AbstractRefreshableApplicationContext和GenericApplicationContext,這裡我們就關心AbstractRefreshableApplicationContext:

AbstractRefreshableApplicationContext

我們要時刻記得上面的AbstractRefreshableConfigApplicationContext類是繼承於AbstractRefreshableApplicationContext的,到這裡我們給張類關系圖以便加深理解:

/**
     * This implementation performs an actual refresh of this context's underlying
     * bean factory, shutting down the previous bean factory (if any) and
     * initializing a fresh bean factory for the next phase of the context's lifecycle.
     */
    @Override
    protected final void refreshBeanFactory() throws BeansException {

         //判斷beanFactory 是否為空
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory(); //設置beanFactory = null
        }
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            //加載Bean定義
            //todo AbstractXmlApplicationContext 子類實現 放入beandefinitionMap中
            loadBeanDefinitions(beanFactory);
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

這裡就看到瞭前篇文章提到一個很重要的方法loadBeanDefinitions,我們再拿出來回顧下加深理解:

/**
     * Load bean definitions into the given bean factory, typically through
     * delegating to one or more bean definition readers.
     * @param beanFactory the bean factory to load bean definitions into
     * @throws BeansException if parsing of the bean definitions failed
     * @throws IOException if loading of bean definition files failed
     * @see org.springframework.beans.factory.support.PropertiesBeanDefinitionReader
     * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
     */
    protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
            throws BeansException, IOException;

這裡因為我們是采用xml配置的,那麼肯定是XmlBeanDefinitionReader無疑,我們再回顧下實現此方法的AbstractXmlApplicationContext:

AbstractXmlApplicationContext

/**
     * Loads the bean definitions via an XmlBeanDefinitionReader.
     * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
     * @see #initBeanDefinitionReader
     * @see #loadBeanDefinitions
     */
    //todo 重載瞭 AbstractRefreshableApplicationContext
    @Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        //忽略代碼。。
        loadBeanDefinitions(beanDefinitionReader);
    }

    /**
     * Load the bean definitions with the given XmlBeanDefinitionReader.
     * <p>The lifecycle of the bean factory is handled by the {@link #refreshBeanFactory}
     * method; hence this method is just supposed to load and/or register bean definitions.
     * @param reader the XmlBeanDefinitionReader to use
     * @throws BeansException in case of bean registration errors
     * @throws IOException if the required XML document isn't found
     * @see #refreshBeanFactory
     * @see #getConfigLocations
     * @see #getResources
     * @see #getResourcePatternResolver
     */
    //bean工廠的生命周期由 refreshBeanFactory 方法來處理
    //這個方法隻是 去加載和註冊 bean定義
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        //ClassPathXmlApplicationContext
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            //抽象AbstractBeanDefinitionReader裡去加載
            reader.loadBeanDefinitions(configLocations);
        }
    }

這裡我們終於到瞭這個configLocations的用武之地,它就傳入瞭XmlBeanDefinitionReader的loadBeanDefinitions方法中。在這裡我們也看到瞭Spring首先會根據configResources加載BeanDefinition,其次才會去根據configLocations配置去加載BeanDefinition。到這裡我們可以學到Spring中對面向對象中封裝,繼承和多態的運用。下篇文章我們繼續剖析Spring關於Xml加載Bean定義的點滴。

以上就是Spring 加載多個xml配置文件的原理分析的詳細內容,更多關於Spring 加載xml配置文件的資料請關註WalkonNet其它相關文章!

推薦閱讀: