上一章我們介紹了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 Map<String, Object> handlerMappings = 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 Map<String, Object> getHandlerMappings() { 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<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(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 @Override 2 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} to 3 * 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 using 12 * 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方法解析具體的標簽