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对象的命名空间,获取不同命名空间的解析器,然后针对同一命名空间里面的不同标签也分配不同的解析器,然后循环遍历这些解析器进行解析。每个标签的详细解析过程,后面分别会进行详解
转载于:https://blog.51cto.com/yoyanda/1717870