spring源码(3)之解析配置文件的过程

spring源码之解析配置文件过程

上篇博文,我们探讨了spring获取配置文件applicationContext.xml的document对象。回想struct2解析struct*.xml,当struct2获取struct*.xml文件的document对象之后,就会循环遍历这个document,然把不同的标签的信息封装到不同的对象中,如<package>标签封装到packageConfig对象,<action>标签封装到actionConfig对象等等。那么spring在获取document对象之后,是不是也是循环遍历document对象,然后针对element进行封装呢?我们先来看下applicationContext.xml的文档结构。

1.applicationContext.xml文档结构

<!--【代码清单】applicationContext.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:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation=" ">
    <!-- 启用自动扫描与装配bean(base-package:cn.thinmore.oa.*)-->
    <context:component-scan base-package="cn.thinmore.shop"></context:component-scan>
    <import resource=""/>
    <alias name="" alias=""/>
    <!-- 导入外部的properties文件 -->
 <context:property-placeholder location="classpath:jdbc.properties" />
    <!-- 配置sessionFactory-->
    <bean id="sessionFactory"
    class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
</bean>

上面为ApplicationContext.xml的大体结构(并不是所有的配置),我们可以看到在配置之前,定义了一堆的头文件。xsi:schemaLocation里面定义的是dtd路径,xmlns\xmlns:context定义的是命名空间(nameSpace),我们下面就这两点做简要说明,详细请去了解xml的知识以及spring的配置知识。

1.1.dtd

引入相应的DTD文件后,就可以在该XML文件,编辑相应的*.XML文件,并在该文件有相应的提示功能。如要编辑struct.xml,引入struct2.dtd后,在 struct.xml文件中就会有struct2的编写规范提示.dtd文件定义了标签的语法。

1.2.命名空间

①什么是XML的命名空间?

为了避免XML的标签同名。XML也拥有命名空间。标签可以放入命名空间中,不同的命名空间中的相同名称标签是不同的标签。如<bean>标签放到xmlns中,<context:*>系列标签放到xmlns:context命名空间中。

②命名空间的语法

<!--【代码清单】:命名空间-->
xmlns:context="http://www.springframework.org/schema/context"

 

xmlns:命名空间的标识

context:命名空间的前缀,作为URL的简化。没有前缀则为默认命名空间

如:

xmlns="http://www.springframework.org/schema/beans"

value:即URL为命名空间的标识,双引号里面的值就是value值

③为什么要讲述命名空间?

因为不同标签放到不同的命名空间中。因此在解析的时候,需要以命名空间为条件,生成不同标签的解析器。如解析bean的BeanDefinitionHolder和解析其他的NameSpaceHolder.这就是spring和struct2解析配置文件不同的地方。struct2是以document里面的子节点作单位进行解析,而spring是以命名空间作为单位对配置文件进行解析的。下面就让我们来看下spring的具体实现。

2.解析document对象

让我们先来回顾下,spring获取document对象的过程。

//【代码清单】得到Document对象
    protectedint doLoadBeanDefinitions(InputSourceinputSource, Resource resource)throws BeanDefinitionStoreException {
        try {
           int validationMode =getValidationModeForResource(resource);
//获取document对象
           Documentdoc = this.documentLoader.loadDocument(
                  inputSource,getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
                  //通过解析dom,然后完成bean在ioc容器中的注册。
           return registerBeanDefinitions(doc, resource);
       }
    }

获取document对象之后,就把document对象和抽象资源resouce作为参数,调用registBeanDefinition()方法进行解析。

//【代码清单】:生成DocumentReader
    publicint registerBeanDefinitions(Documentdoc, Resource resource) throws BeanDefinitionStoreException {
       // 定义文档解析器,这个已过期,新版本用XmlReaderContextreader.
       if (this.parserClass != null) {
           XmlBeanDefinitionParserparser =
                  (XmlBeanDefinitionParser)BeanUtils.instantiateClass(this.parserClass);
           returnparser.registerBeanDefinitions(this, doc, resource);
       }
       //首先得到XmlReaderContext,具体是BeanDefinitionDocumentReader来处理xml的bean定义文件. BeanDefinitionDocumentReader是一个接口,DefaultBeanDefinitionDocumentReader是其实现类
      BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
       int countBefore =getRegistry().getBeanDefinitionCount();
         //具体的注册过程
       documentReader.registerBeanDefinitions(doc,createReaderContext(resource));
       return getRegistry().getBeanDefinitionCount() - countBefore;
    }

xml文档有解析器xmlReader,同样,document对象也应有解析器。所以,在解析前也实例化一个document的解析器beanDefinitonDocumentReader.

2.1.解析Document文档

//【代码清单】:委托BeanDefinitionParserDelegate解析文档
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
       //注意这里传进去了一个ReaderContext,这里有个注册器
       this.readerContext =readerContext;
       //获得文档根元素
       Elementroot = doc.getDocumentElement();
       BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
       //空方法,可以添加解析文档前的工作
       preProcessXml(root);
        //解析开始
       parseBeanDefinitions(root,delegate);
        //空方法,可以添加解析文档后的工作
       postProcessXml(root);
    }

document解析器会委托一个beanDefinitionParseDelegatelegate去解析document文档。这个namespaceHandlerResolver是一个加载spring.handlers文件的类,这个spring.handlers文件是命名空间的对照表,spring通过这个对照表,根据不同的命名空间空间,实例化不同的解析器.下面会讲到。

//【代码清单】readerContext创建过程
    protected XmlReaderContext createReaderContext(Resource resource) {
       if (this.namespaceHandlerResolver == null) {
           this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
       }
   //实例化一个XmlReaderContext对象
       returnnew XmlReaderContext(resource, this.problemReporter, this.eventListener,
              this.sourceExtractor, this, this.namespaceHandlerResolver);
    }

2.2.选择标签解析器

上面说到,spring解析配置文件是以命名空间为单位进行解析的。所以这里会对document对象里面不同命名空间的标签分配不同的解析器。

//【代码清单】选择标签解析器
protectedvoid parseBeanDefinitions(Elementroot, BeanDefinitionParserDelegate delegate) {
      //判断根节点的命名空间是否是默认空间,这里是bean
       if(delegate.isDefaultNamespace(root.getNamespaceURI())) {
           NodeListnl = root.getChildNodes();
         //for循环遍历
           for (int i = 0; i <nl.getLength(); i++) {
              Nodenode = nl.item(i);
              if (node instanceof Element) {
                  Elementele = (Element) node;
                  //判断节点的命名空间
                  StringnamespaceUri = ele.getNamespaceURI();
                  if(delegate.isDefaultNamespace(namespaceUri)) {
         //如果是默认空间xmlns="http://www.springframework.org/schema/beans"中的标签
                     parseDefaultElement(ele,delegate);
                  }
                  else {
                      //如果不是默认空间中的标签
                     delegate.parseCustomElement(ele);
                  }
              }
           }
       }
       else {
           delegate.parseCustomElement(root);
       }
    }

总体来说,命名空间分为默认命名空间和非默认命名空间。

【代码清单】默认空间解析器的标签解析
privatevoid parseDefaultElement(Elementele, BeanDefinitionParserDelegate delegate) {
       if (DomUtils.nodeNameEquals(ele,IMPORT_ELEMENT)) {
           //解析import标签
           importBeanDefinitionResource(ele);
       }
       elseif (DomUtils.nodeNameEquals(ele,ALIAS_ELEMENT)) {
          //解析alia别名标签
           processAliasRegistration(ele);
       }
       elseif (DomUtils.nodeNameEquals(ele,BEAN_ELEMENT)) {
            //解析bean标签
           processBeanDefinition(ele,delegate);
       }
    }

默认命名空间里面的是import标签、alia标签和bean标签。

//【代码清单】不是默认空间的解析器的解析
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd){
       //获得命名空间
       StringnamespaceUri = ele.getNamespaceURI();
       //根据不同的命名空间,获得相应的解析器
       NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
       //调用解析器的parse方法解析相应的标签
       return handler.parse(ele,new ParserContext(this.readerContext, this, containingBd));
    }

非默认命名空间的标签也有很多,如切面编程的<aop>和事务管理的<transation>等等。因此在解析非命名空间的标签前也会分配相应的解析器NamespaceHander,这里用到了适配器模式,namespaceHander是一个接口,这里会根据不同的命名空间实例化不同的解析器

//【代码清单】选择namespaceHandler
public NamespaceHandler resolve(String namespaceUri) {
        //先得到namespaceHandler对照表
       Map handlerMappings = getHandlerMappings();
        //根据命名空间匹配相应的namespaceHandler
       Object handlerOrClassName = handlerMappings.get(namespaceUri);
       if (handlerOrClassName == null) {
             //如果找不到,返回null
           returnnull;
       }
       elseif (handlerOrClassName instanceof NamespaceHandler) {
            //如果是namespaceHandler实现类,则返回相应的namespaceHandler
           return (NamespaceHandler) handlerOrClassName;
       }
       else {
           String className = (String) handlerOrClassName;
           try {
             //如果是className,则实例化相应的namespaceHandler
              Class handlerClass = ClassUtils.forName(className,this.classLoader);
       NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);
                //调用该namespaceHandler的init()方法,给每个命名空间里面的每个标签分配解析器,对应关系放到一个map中
              namespaceHandler.init();
                 //缓存起来,以后解析其他的Context标签,就不用再解析对照表
              handlerMappings.put(namespaceUri, namespaceHandler);
              return namespaceHandler;
           }
       }
    }

spring是如何根据命名空间实现不同的namespace解析器的呢?我们从上面这段代码看到,spring是根据对照表来实例化不同的类的。这个对照表是怎么来的呢?

//【代码清单】得到namespaceHandler对照表
private Map getHandlerMappings(){
       if (this.handlerMappings == null) {
           try {
            //载入属性文件
              Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);    
              this.handlerMappings = new HashMap(mappings);
           }
       }
       returnthis.handlerMappings;
    }

从这里,我们可以到:得到namespaceHandler对照表,只是根据handleerMappingLocation加载了一个属性文件,这个handleerMappingLocation是什么呢?我们看NamespaceHandlerResolver的构造函数

//【代码清单】NamespaceHandlerResolver的构造函数
publicstaticfinal String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
public DefaultNamespaceHandlerResolver(){
       this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
    }

因此,从这里看到,namespaceHandler对照表是存放到spring.handlers文件中。加载这个文件,就可以得到namespaceHandler对照表了。

//【代码清单】spring.handlers
http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/jms=org.springframework.jms.config.JmsNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler
http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler

解析这个文件,把解析结果以命名空间为key值,namespacehandlers为value值保存到一个map中,然后根据命名空间就可以得到相应的namespaceHandler了。例如:根据context的命名空间http\://www.springframework.org/schema/context就可以得到org.springframework.context.config.ContextNamespaceHandler这个ContextNamespaceHandler。

//【代码清单】获得相应的namespaceHandler
  Object handlerOrClassName =handlerMappings.get(namespaceUri);

上面选择namespaceHandler的时候,还会对这个namespaceHandler进行初始化

//【代码清单】获取后调用该namespaceHandler的init方法
          //调用该namespaceHandler的init()方法,给每个命名空间里面的每个标签分配解析器,对应关系放到一个map中
              namespaceHandler.init();
                 //缓存起来,以后解析其他的Context标签,就不用再解析对照表
              handlerMappings.put(namespaceUri, namespaceHandler);
                 return namespaceHandler;

这个初始化做了些什么工作呢?我们就选aop的namespaceHandler来瞧瞧

//【代码清单】调用这个AopNamespaceHandler的init()方法
    publicvoid init() {
       // In 2.0 XSD as well as in2.1 XSD.
       registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
       registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
       registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
       // Only in 2.0 XSD: moved tocontext namespace as of 2.1
       registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }

这个init()方法对每个aop标签都调用了regist方法。

//【代码清单】registerBeanDefinitionParser
privatefinal Map parsers = new HashMap();
protectedfinalvoid registerBeanDefinitionParser(StringelementName, BeanDefinitionParser parser) {
          //把每个标签的parser放到map中建立映射关系
       this.parsers.put(elementName, parser);
    }

由此可以看出,registerBeanDefinitionParser方法是旨在给每个标签分配一个解析器。例如<aop:config/>的ConfigBeanDefinitionParser。把这些解析器都放到一个map中,到时候解析aop命名空间的时候,再从这个map中取出相应的解析器解析相应的标签

//【代码清单】parse解析
publicfinal BeanDefinition parse(Element element, ParserContextparserContext) {
       //根据element找到标签相应的parser
       return findParserForElement(element,parserContext).parse(element, parserContext); //调用parser的parse方法
    }

parse()方法主要完成了两步工作:

1.根据标签名,找到相应的解析器;

2.调用该解析器解析该标签

【代码清单】找到相应的parser
private BeanDefinitionParser findParserForElement(Element element,ParserContext parserContext) {
        //从init()方法中的map中取出element标签的parser
       BeanDefinitionParser parser = (BeanDefinitionParser) this.parsers.get(element.getLocalName());
       return parser;
    }

相应的具体解析过程,后续详解。

3.总结

这篇博文探讨了spring解析配置文件的过程:通过document对象的命名空间,获取不同命名空间的解析器,然后针对同一命名空间里面的不同标签也分配不同的解析器,然后循环遍历这些解析器进行解析。每个标签的详细解析过程,后面分别会进行详解

时间: 2024-10-12 18:10:37

spring源码(3)之解析配置文件的过程的相关文章

【Spring源码分析】原型Bean实例化过程、byName与byType及FactoryBean获取Bean源码实现

原型Bean加载过程 之前的文章,分析了非懒加载的单例Bean整个加载过程,除了非懒加载的单例Bean之外,Spring中还有一种Bean就是原型(Prototype)的Bean,看一下定义方式: 1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi=&qu

Spring源码入门——XmlBeanDefinitionReader解析

接上篇[] ,我们看到BeanDefinitionReader解决的是从资源文件(xml,propert)到BeanDefinition集合的过程.所以BeanDefinitionReader接口有两个实现版本. BeanDefinitionReader的接口声明,ResourceLoader是spring中解决Resource加载的操作.四个loadBeanDefinitions就是重载解决单个或者多个资源文件的处理问题. loadBeanDefinitions 是加载BeanDefiniti

Spring源码入门——AnnotationBeanNameGenerator解析

---恢复内容开始--- 接上篇,上篇解析了DefaultBeanGenerator生成bean name的过程(http://www.cnblogs.com/jason0529/p/5272265.html ), 本篇我们继续解析另一类bean name生成方式. spring定义bean有两种模式,配置文件(xml,properties)和注解.注:jpa的声明接口生成bean应该可以算第三种模式,这里不讨论. 对两种bean定义方式,spring提供了两种不同的bean name实现方式去

Spring源码入门——DefaultBeanNameGenerator解析

我们知道在spring中每个bean都要有一个id或者name标示每个唯一的bean,在xml中定义一个bean可以指定其id和name值,但那些没有指定的,或者注解的spring的beanname怎么来的的?就是BeanNameGenerator接口实现的特性. <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"><prop

Spring源码分析专题——目录

Spring源码分析专题 -- 阅读指引 IOC容器 Spring源码分析专题 -- IOC容器启动过程(上篇) Spring源码分析专题 -- IOC容器启动过程(中篇) Spring源码分析专题 -- IOC容器启动过程(下篇) Spring源码分析专题 -- IOC容器依赖注入 SpringMVC Spring源码分析专题 -- SpringMVC IOC容器依赖注入 Spring源码分析专题 -- SpringMVC原理分析 Spring源码分析专题 -- SpringAOP源码分析 S

Spring源码分析专题 —— 阅读指引

阅读源码的意义 更深入理解框架原理,印象更深刻 学习优秀的编程风格.编程技巧.设计思想 解决实际问题,如修复框架中的bug,或是参考框架源码,结合实际业务需求编写一个独有的框架 阅读源码的方法 首先是要有一定的编程经验.如果连业务代码都写得不流畅那是不建议阅读源码的,因为基础不好的情况下一是阅读困难,二是无法静下心理解,每看两行就会纠结花大量时间在源码上是否值得,感觉不如写多两行业务代码来得有价值. 要有耐心.一篇关于源码的文章可能需要阅读两三小时以上才能读完,如果没有这个觉悟,可能看到一半就转

spring源码剖析(二)Spring默认标签解析及注册实现

在使用spring的时候,我也经常会使用到bean标签,beans标签,import标签,aop标签等. 下面主要为读者介绍spring的默认的自带标签的解析流程. 验证模式(DTD&XSD) dtd基本已被淘汰,现在spring的验证模式基本都是采用xsd文件作为xml文档的验证模式,通过xsd文件可以检查该xml是否符合规范,是否有效.在使用xsd文件对xml文档进行校验的时候,除了要名称空间外(xmlns="http://www.springframework.org/schema

【Spring源码分析】配置文件读取流程

前言 Spring配置文件读取流程本来是和http://www.cnblogs.com/xrq730/p/6285358.html一文放在一起的,这两天在看Spring自定义标签的时候,感觉对Spring配置文件读取流程还是研究得不够,因此将Spring配置文件读取流程部分从之前的文章拆出来单独成为一文. 为了看一下Spring配置文件加载流程,先定义一个bean.xml: 1 <?xml version="1.0" encoding="UTF-8"?>

Spring 源码解析之ViewResolver源码解析(四)

Spring 源码解析之ViewResolver源码解析(四) 1 ViewResolver类功能解析 1.1 ViewResolver Interface to be implemented by objects that can resolve views by name. View state doesn't change during the running of the application, so implementations are free to cache views. I