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