Spring源码解析之配置文件的解析

此篇博客篇幅较长,请耐心阅读,可能我文笔不太好,但是请仔细看博客中的代码的注释和说明,这样能帮助你理解。有什么问题可以回复。  

直接进入正题,在使用Spring时,我们经常要进行大量的配置来告诉Spring,我们需要什么样的功能。但是配置文件又是怎么被Spring解析变成Spring所需要的东西呢。

  Spring中有一个接口类,名叫BeanDefinition,配置文件中的<bean>标签中有着class,scope,而BeanDefinition提供了相应的beanClass,scope属性,BeanDefinition中的属性和<bean>中的属性是一一对应的。

  抠张图来看看BeanDefinition的种类以及他们之间的关系

  

                                                          图1

在Spring解析配置文件时,配置文件中的信息会转换为BeanDefinition存在于内存中。

接下来我们用一个小例子来逐步的进行阅读源码

    @Test
    public void run(){
        ApplicationContext context =
                new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

        UserService bean = (UserService) context.getBean("userServiceImpl");

        bean.sayHello();
    }

​
​

配置文件如下

<!--加载配置文件-->
    <context:property-placeholder location="classpath:db.properties" />
    <!--配置数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxActive" value="10" />
    </bean>
    <!--SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:SqlMapConfig.xml"/>
        <property name="typeAliasesPackage" value="demo"/>
        <property name="mapperLocations" value="UserMapper.xml"/>
    </bean>
    <!--Mapper文件扫描-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="mapper"/>
    </bean>
    <context:component-scan base-package="service"/>

                                                                    配置文件图2

 接下来让我们Debug来逐步去看Spring是如何解析配置文件的。

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
        super(parent);
        //将传入的配置文件名作为属性赋值给成员变量
        this.setConfigLocations(configLocations);
        if (refresh) {
            //Spring一切的开始
            this.refresh();
        }
}

进入到refresh方法中一探究竟

public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        synchronized(this.startupShutdownMonitor) {
            //准备刷新的上下文环境,如你只是使用Spring,你可以不用理会这个方法
            this.prepareRefresh();
            //OK,这个方法就是解析XML文件的方法,当这个方法执行完时,XML文件已经解析完了
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                //初始化剩下实例,也就是我们自己写的那些代码,Service,Controller等等
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }
                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }
        }
    }

如果你要想看Spring解析配置文件的步骤,只需要看

ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();这一行代码就足够了。让我们一起来挑一些重要的方法来看吧。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        //初始化BeanFactory,并进行读取XML文件,并将XML内容转换为BeanDefintion对象存储
        this.refreshBeanFactory();
        //返回当前实体的beanFactory属性
        ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Bean factory for " + this.getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }

在这个方法中,我们只能看到又调用了refreshBeanFactory方法,所以我们继续往下看

protected final void refreshBeanFactory() throws BeansException {
        if (this.hasBeanFactory()) {
            this.destroyBeans();
            this.closeBeanFactory();
        }
        try {
            //创建DefaultListableBeanFactory对象
            DefaultListableBeanFactory beanFactory = this.createBeanFactory();
            beanFactory.setSerializationId(this.getId());
            this.customizeBeanFactory(beanFactory);
            //初始化DocumentReader,准备解析XML文件
            this.loadBeanDefinitions(beanFactory);
            Object var2 = this.beanFactoryMonitor;
            synchronized(this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        } catch (IOException var5) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
        }
    }

可以看到,我们还是没有找到解析XML文件的地方,但是在这里注意两点,DefaultListableBeanFactory对象是整个bean加载的核心部分,是Spring注册及加载Bean的默认实现,也就是说,我们从XML读取到的信息转换为BeanDefintion之后都注册到这个对象之中。

接下来进入loadBeanDefinitions方法之后继续追寻

    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        //以beanFactory为参数创建XmlBeanDefinitionReader对象
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        //设置环境变量
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
        this.initBeanDefinitionReader(beanDefinitionReader);
        //解析XML文件(怎么还没到解析XML,好烦啊)
        this.loadBeanDefinitions(beanDefinitionReader);
    }

进入loadBeanDefinitions(XmlBeanDefinitionReader reader)方法继续寻找

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        Resource[] configResources = this.getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        //获取配置文件名
        String[] configLocations = this.getConfigLocations();
        if (configLocations != null) {

            //好,离真正解析XML的方法又近了一步
            reader.loadBeanDefinitions(configLocations);
        }

    }

还是没有看到XML的解析,不过我们又近了一步,不急嘛,肯定会到的。继续深入查看reader.loadBeanDefinitions(configLocations);

    public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
        Assert.notNull(locations, "Location array must not be null");
        int counter = 0;
        String[] var3 = locations;
        int var4 = locations.length;
        for(int var5 = 0; var5 < var4; ++var5) {
            String location = var3[var5];
            //哎,又近了一步
            counter +&
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值