码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2015-11-29 10:46:36      阅读:341      评论:0      收藏:0      [点我收藏+]

标签:spring源码 spring解析配置文件

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值,namespacehandlersvalue值保存到一个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对象的命名空间,获取不同命名空间的解析器,然后针对同一命名空间里面的不同标签也分配不同的解析器,然后循环遍历这些解析器进行解析。每个标签的详细解析过程,后面分别会进行详解

     

 


本文出自 “总有贱人想害朕。” 博客,请务必保留此出处http://yoyanda.blog.51cto.com/9675421/1717870

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

标签:spring源码 spring解析配置文件

原文地址:http://yoyanda.blog.51cto.com/9675421/1717870

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!