Spring 加载多个xml配置文件的原理分析

 更新时间:2021年06月22日 14:20:31   作者:沉迷Spring  
我们知道Spring一次可以加载多个Bean定义的Xml配置文件,我们可以设想下如果让我们来做我们会怎么做?我估计会根据配置文件的顺序依次读取并加载,那再来看看Spring是如何做的?

示例

先给出两个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配置文件的资料请关注脚本之家其它相关文章!

相关文章

  • 详解Spring Boot使用系统参数表提升系统的灵活性

    详解Spring Boot使用系统参数表提升系统的灵活性

    Spring Boot项目中常有一些相对稳定的参数设置项,其作用范围是系统级的或模块级的,这些参数称为系统参数。这些变量以参数形式进行配置,从而提高变动和扩展的灵活性,保持代码的稳定性
    2021-06-06
  • java容器类知识点详细总结

    java容器类知识点详细总结

    这篇文章主要介绍了java容器类知识点详细总结,
    2019-06-06
  • HashMap原理的深入理解

    HashMap原理的深入理解

    这篇文章主要介绍了对HashMap原理的理解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • 用Java将字符串的首字母转换大小写

    用Java将字符串的首字母转换大小写

    在项目开发的时候会需要统一字符串的格式,比如首字母要求统一大写或小写,那用Java如何实现这一功能?下面一起来学习学习。
    2016-08-08
  • Spring @Bean 修饰方法时注入参数的操作方法

    Spring @Bean 修饰方法时注入参数的操作方法

    对于 Spring 而言,IOC 容器中的 Bean 对象的创建和使用是一大重点,Spring 也为我们提供了注解方式创建 bean 对象:使用 @Bean,这篇文章主要介绍了Spring @Bean 修饰方法时如何注入参数,需要的朋友可以参考下
    2023-10-10
  • MybatisPlus分页失效不起作用的解决

    MybatisPlus分页失效不起作用的解决

    在使用MybatisPlus的selectPage时发现分页不起作用,每次返回的都是全部的数据,本文就来介绍一下MybatisPlus分页失效不起作用的解决,感兴趣的可以了解一下
    2024-03-03
  • mybatis 自定义实现拦截器插件Interceptor示例

    mybatis 自定义实现拦截器插件Interceptor示例

    这篇文章主要介绍了mybatis 自定义实现拦截器插件Interceptor,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • 基于Feign实现异步调用

    基于Feign实现异步调用

    近期,需要对之前的接口进行优化,缩短接口的响应时间,但是springcloud中的feign是不支持传递异步化的回调结果的,因此有了以下的解决方案,记录一下,需要的朋友可以参考下
    2021-05-05
  • Java中Word与PDF转换为图片的方法详解

    Java中Word与PDF转换为图片的方法详解

    这篇文章主要为大家详细介绍了如何使用Java实现将Word与PDF转换为图片,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-10-10
  • 实例化JFileChooser对象报空指针异常问题的解决办法

    实例化JFileChooser对象报空指针异常问题的解决办法

    今天小编就为大家分享一篇关于实例化JFileChooser对象报空指针异常问题的解决办法,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-02-02

最新评论