上一章我们介绍了Spring如何创建bean,<bean></bean>的命名空间是Spring默认的命名空间,那么对于<tx:advice></tx:advice>、<mvc:annotation-driven></mvc:annotation-driven>这种自定义的标签该如何解析呢?下面就以这几个标签为例进行说明,同时也说明下声明式Spring事物,还有我们在进行SpringMVC开发时配置的<mvc:annotation-driven/>到底起什么作用也会简单介绍。
现在我们回到org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法中
//org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader 1 /** 2 * Parse the elements at the root level in the document: 3 * "import", "alias", "bean". 4 * @param root the DOM root element of the document 5 */ 6 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { 7 if (delegate.isDefaultNamespace(root)) { 8 NodeList nl = root.getChildNodes(); 9 for (int i = 0; i < nl.getLength(); i++) {10 Node node = nl.item(i);11 if (node instanceof Element) {12 Element ele = (Element) node;13 if (delegate.isDefaultNamespace(ele)) {14 parseDefaultElement(ele, delegate);15 }16 else {17 delegate.parseCustomElement(ele);18 }19 }20 }21 }22 else {23 delegate.parseCustomElement(root);24 }25 }
第17行,解析自定义的命名空间标签,跟踪方法
//org.springframework.beans.factory.xml.BeanDefinitionParserDelegate 1 public BeanDefinition parseCustomElement(Element ele) { 2 return parseCustomElement(ele, null); 3 } 4 5 public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { 6 String namespaceUri = getNamespaceURI(ele); 7 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); 8 if (handler == null) { 9 error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);10 return null;11 }12 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));13 }
第6行,获取标签的命名空间,第7行,获取命名空间的处理器,第12行,调用命名空间处理器的parse方法解析标签。所有的自定义的命名空间的标签都是按照这种方式进行解析的,下面来看下具体都是怎么实现的
this.readerContext是通过org.springframework.beans.factory.xml.XmlBeanDefinitionReader的createReaderContext方法创建的
//org.springframework.beans.factory.xml.XmlBeanDefinitionReader 1 /** 2 * Create the { @link XmlReaderContext} to pass over to the document reader. 3 */ 4 public XmlReaderContext createReaderContext(Resource resource) { 5 return new XmlReaderContext(resource, this.problemReporter, this.eventListener, 6 this.sourceExtractor, this, getNamespaceHandlerResolver()); 7 } 8 9 /**10 * Lazily create a default NamespaceHandlerResolver, if not set before.11 * @see #createDefaultNamespaceHandlerResolver()12 */13 public NamespaceHandlerResolver getNamespaceHandlerResolver() {14 if (this.namespaceHandlerResolver == null) {15 this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();16 }17 return this.namespaceHandlerResolver;18 }19 20 /**21 * Create the default implementation of { @link NamespaceHandlerResolver} used if none is specified.22 * Default implementation returns an instance of { @link DefaultNamespaceHandlerResolver}.23 */24 protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {25 return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());26 }
根据上边代码我们知道this.readerContext.getNamespaceHandlerResolver()方法返回的是DefaultNamespaceHandlerResolver实例,进入resolve方法
//org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver 1 /** 2 * Locate the { @link NamespaceHandler} for the supplied namespace URI 3 * from the configured mappings. 4 * @param namespaceUri the relevant namespace URI 5 * @return the located { @link NamespaceHandler}, or { @code null} if none found 6 */ 7 @Override 8 public NamespaceHandler resolve(String namespaceUri) { 9 MaphandlerMappings = getHandlerMappings();10 Object handlerOrClassName = handlerMappings.get(namespaceUri);11 if (handlerOrClassName == null) {12 return null;13 }14 else if (handlerOrClassName instanceof NamespaceHandler) {15 return (NamespaceHandler) handlerOrClassName;16 }17 else {18 String className = (String) handlerOrClassName;19 try {20 Class handlerClass = ClassUtils.forName(className, this.classLoader);21 if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {22 throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +23 "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");24 }25 NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);26 namespaceHandler.init();27 handlerMappings.put(namespaceUri, namespaceHandler);28 return namespaceHandler;29 }30 catch (ClassNotFoundException ex) {31 throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +32 namespaceUri + "] not found", ex);33 }34 catch (LinkageError err) {35 throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +36 namespaceUri + "]: problem with handler class file or dependent class", err);37 }38 }39 }
关注第24--28行,根据Class对象实例化NamespaceHandler,调用NamespaceHandler实例的init()方法,把命名空间和NamespaceHandler实例映射关系放入handlerMappings,返回NamespaceHandler实例。那么handlerMappings是怎么来的呢,进入getHandlerMappings()方法
org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver 1 /** 2 * Load the specified NamespaceHandler mappings lazily. 3 */ 4 private MapgetHandlerMappings() { 5 if (this.handlerMappings == null) { 6 synchronized (this) { 7 if (this.handlerMappings == null) { 8 try { 9 Properties mappings =10 PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);11 if (logger.isDebugEnabled()) {12 logger.debug("Loaded NamespaceHandler mappings: " + mappings);13 }14 Map handlerMappings = new ConcurrentHashMap (mappings.size());15 CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);16 this.handlerMappings = handlerMappings;17 }18 catch (IOException ex) {19 throw new IllegalStateException(20 "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);21 }22 }23 }24 }25 return this.handlerMappings;26 }
第9、10行,加载所有Spring的jar文件中的"META-INF/spring.handlers"属性文件,第15行,把属性文件转换成map,第16行,转换后的map赋值到this.handlerMappings。
我们来看看spring-tx-4.0.2.RELEASE.jar和spring-webmvc-4.0.2.RELEASE.jar中spring.handlers文件从上边两个图中我们可以清楚的看到,每个命名空间都对应命名空间处理器的类全路径。进入org.springframework.transaction.config.TxNamespaceHandler的init方法,看下这个方法都做了那些事情
//org.springframework.transaction.config.TxNamespaceHandler 1 @Override2 public void init() {3 registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());4 registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());5 registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());6 }
//org.springframework.beans.factory.xml.NamespaceHandlerSupport 1 /**2 * Subclasses can call this to register the supplied { @link BeanDefinitionParser} to3 * handle the specified element. The element name is the local (non-namespace qualified)4 * name.5 */6 protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {7 this.parsers.put(elementName, parser);8 }
注意registerBeanDefinitionParser方法是在父类org.springframework.beans.factory.xml.NamespaceHandlerSupport中。init()主要是注册了advice、annotation-driven、jta-transaction-manager标签对应的解析器。
回到org.springframework.beans.factory.xml.BeanDefinitionParserDelegate的parseCustomElement方法//org.springframework.beans.factory.xml.BeanDefinitionParserDelegate 1 public BeanDefinition parseCustomElement(Element ele) { 2 return parseCustomElement(ele, null); 3 } 4 5 public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { 6 String namespaceUri = getNamespaceURI(ele); 7 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); 8 if (handler == null) { 9 error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); 10 return null; 11 } 12 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); 13 }
现在NamespaceHandler实例已经构建完成,同时也执行了init()方法,再来看下parse()方法(在父类org.springframework.beans.factory.xml.NamespaceHandlerSupport中),进入方法
1 /** 2 * Parses the supplied { @link Element} by delegating to the { @link BeanDefinitionParser} that is 3 * registered for that { @link Element}. 4 */ 5 @Override 6 public BeanDefinition parse(Element element, ParserContext parserContext) { 7 return findParserForElement(element, parserContext).parse(element, parserContext); 8 } 9 10 /**11 * Locates the { @link BeanDefinitionParser} from the register implementations using12 * the local name of the supplied { @link Element}.13 */14 private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {15 String localName = parserContext.getDelegate().getLocalName(element);16 BeanDefinitionParser parser = this.parsers.get(localName);17 if (parser == null) {18 parserContext.getReaderContext().fatal(19 "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);20 }21 return parser;22 }
第16行,得到的是在org.springframework.transaction.config.TxNamespaceHandler的init中注册的各个标签对应的解析器,<tx:advice/>对应的是TxAdviceBeanDefinitionParser实例,所以第7行调用的parse方法就是TxAdviceBeanDefinitionParser中的parse方法,其它的标签也是调用各自的解析器进行解析的。其它的命名空间也是同样的处理方式。
总结Spring解析自定义标签
1、在BeanDefinitionParserDelegate的parseCustomElement()中调用DefaultNamespaceHandlerResolver的resolve方法
2、resolve方法根据属性文件创建了各个命名空间NameSpaceHandler实例,调用NameSpaceHandler的init()方法注册每个标签所对应的解析器
3、BeanDefinitionParserDelegate的parseCustomElement()中调用DefaultNamespaceHandlerResolver的parse方法
4、parse方法查找第2步注册的解析器,调用其parse方法解析具体的标签