在上一篇解析<bean>標簽及其所有子標簽我們詳細探討了如何使用<bean>標簽來創建一個BeanDefintion對象。這一篇我們開始探討一下sPRing如何處理其它命名空間的xml標簽,比如spring擴展的http://www.springframework.org/schema/context、http://www.springframework.org/schema/p和http://www.springframework.org/schema/aop命名空間。
經過前面的探討,我們知道XmlBeanDefinitionReader使用BeanDefinitionDocumentReader對象把Document對象中包含的配置信息轉換成BeanDefinition對象并把它注冊到BeanDefintionRegistry對象中。默認使用DefaultBeanDefinitionDocumentReader來操作Document對象。在DefaultBeanDefinitionDocumentReader的實現中,它的責任是遍歷xml根節點下的子節點,并把處理bean標簽和自定義命名空間的標簽(比如aop:,context:,p:等)的細節委托給BeanDefinitionParserDelegate對象,BeanDefinitionParserDelegate才是真正解析配置文件的地方。
下面是DefaultBeanDefinitionDocumentReader對象使用BeanDefinitionParserDelegate對象來處理自定義命名空間的標簽的入口,
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // 檢查root節點的命名空間是否為默認命名空間 // spring配置文件中默認的命名空間為"http://www.springframework.org/schema/beans" if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); // 遍歷root節點下的所有子節點 for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; // 檢查子節點的命名空間是否為默認命名空間 if (delegate.isDefaultNamespace(ele)) { // 解析默認命名空間的元素節點 parseDefaultElement(ele, delegate); } else { // 解析自定義元素節點 delegate.parseCustomElement(ele); } } } } else { // 解析自定義元素節點 delegate.parseCustomElement(root); } }parseBeanDefinitions方法的主要事情是區分節點標簽是否是默認命名空間的標簽,以及遍歷根節點下的子節點。在這里對于任何一個節點,如果是默認命名空間的則調用DefaultBeanDefinitionDocumentReader方法parseDefaultElement處理,否則調用BeanDefinitionParserDelegate 的parseCustomElement方法來處理。這篇文章的主題是探討spring如何解析自定義命名空間的標簽,因此我們的入口是parseCustomElement方法,如下是這個方法的源代碼。
public BeanDefinition parseCustomElement(Element ele) { return parseCustomElement(ele, null); }parseCustomElement(Element ele)方法把處理節點的任務傳遞給parseCustomElement(Element ele, BeanDefinition containingBd)方法,這個方法的源碼如下。
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { // 獲取命名空間的uri String namespaceUri = getNamespaceURI(ele); // 根據NamespaceHandlerResolver對象獲取命名空間的處理器NamespaceHandler對象。 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } // 調用NamespaceHandler的parse方法處理節點 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }parseCustomElement(Element ele, BeanDefinition containingBd)首先通過節點的命名空間uri字符串獲取命名空間處理器NamespaceHandler對象,然后調用處理器的parse方法處理節點。spring定義了很多命名空間和它們對于的處理器類,因此,我們在這里需要清楚的是如何獲取NamespaceHandler對象。
下面是spring定義的NamespaceHandler接口的代碼。
public interface NamespaceHandler { /** * DefaultBeanDefinitionDocumentReader實例化一個NamespaceHandler對象的時候會調用此方法 */ void init(); /** * 解析指定的的Element對象,并返回一個BeanDefinition對象。 */ BeanDefinition parse(Element element, ParserContext parserContext); /** * 解析指定的節點,并裝飾指定的BeanDefinitionHolder對象,最后返回一個已經裝飾的BeanDefinitionHolder對象。 * 這個方法在parse方法之后被調用 */ BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);}Spring定義了NamespaceHandlerResolver接口來獲取命名空間處理器,這個接口的定義如下。
public interface NamespaceHandlerResolver { /** * 解析命名空間的URI字符串并返回一個對應的NamespaceHandler對象,如果沒有找到,則返回null */ NamespaceHandler resolve(String namespaceUri);}NamespaceHandlerResolver 接口只定義了一個方法,并且Spring給出了一個默認的實現,那就是DefaultNamespaceHandlerResolver類,我們來看看這個類的resolve(String namespaceUri)方法,源代碼如下。
@Override public NamespaceHandler resolve(String namespaceUri) { Map<String, Object> handlerMappings = getHandlerMappings(); Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler) handlerOrClassName; } else { String className = (String) handlerOrClassName; try { Class<?> handlerClass = ClassUtils.forName(className, this.classLoader); // 檢查handlerClass 是否實現了NamespaceHandler接口 if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) { throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface"); } // 實例化NamespaceHandler對象 NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); // 初始化NamespaceHandler對象 namespaceHandler.init(); // 把NamespaceHandler對象緩存到handlerMappings對象中 handlerMappings.put(namespaceUri, namespaceHandler); return namespaceHandler; } catch (ClassNotFoundException ex) { throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found", ex); } catch (LinkageError err) { throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]: problem with handler class file or dependent class", err); } } }DefaultNamespaceHandlerResolver的resolve方法通過調用getHandlerMappings()方法獲得NamespaceHandler對象與命名空間URI的映射表,并從這個映射表中取到并返回對應的NamespaceHandler對象。resolve方法的重點就在于NamespaceHandler對象映射表的獲取,下面看看getHandlerMappings()方法的源代碼。
private Map<String, Object> getHandlerMappings() { if (this.handlerMappings == null) { synchronized (this) { if (this.handlerMappings == null) { try { // 這里的handlerMappingsLocation默認為"META-INF/spring.handlers" Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); if (logger.isDebugEnabled()) { logger.debug("Loaded NamespaceHandler mappings: " + mappings); } // 創建一個線程安全的Map對象 Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size()); // 把Properties對象中的key-value復制到handlerMappings中國 CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); this.handlerMappings = handlerMappings; } catch (IOException ex) { throw new IllegalStateException( "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex); } } } } return this.handlerMappings; }getHandlerMappings在第一次調用的時候會通過DefaultNamespaceHandlerResolver的handlerMappingsLocation屬性值(默認為META-INF/spring.handlers)獲取classes路徑和所有jar包中有的相應資源,我們來看看PropertiesLoaderUtils是怎么使用 loadAllProperties(String resourceName, ClassLoader classLoader)加載資源的,代碼如下。
public static Properties loadAllProperties(String resourceName, ClassLoader classLoader) throws IOException { Assert.notNull(resourceName, "Resource name must not be null"); ClassLoader classLoaderToUse = classLoader; if (classLoaderToUse == null) { // 獲取默認的ClassLoader對象 classLoaderToUse = ClassUtils.getDefaultClassLoader(); } // 使用ClassLoader對象來加載class路徑和所有jar包下所匹配的資源 Enumeration<URL> urls = (classLoaderToUse != null ? classLoaderToUse.getResources(resourceName) : ClassLoader.getSystemResources(resourceName)); Properties props = new Properties(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); URLConnection con = url.openConnection(); // 為JNLPCachedJarURLConnection啟動緩存 ResourceUtils.useCachesIfNecessary(con); InputStream is = con.getInputStream(); try { if (resourceName.endsWith(".xml")) { // 加載xml文件 props.loadFromXML(is); } else { // 加載properties文件 props.load(is); } } finally { is.close(); } } return props; }loadAllProperties方法從classes路徑下和所有jar包中獲取所有匹配的資源,并把這些資源文件的內容都保存到同一個Properties對象中作為loadAllProperties方法的返回值。
我們看看Spring都創建了哪些spring.handlers文件及其內容。 a. spring-beans包下的
http/://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandlerhttp/://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandlerhttp/://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandlerb. spring-context包下的
http/://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandlerhttp/://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandlerhttp/://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandlerhttp/://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandlerhttp/://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandlerc. spring-aop包下的
http/://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandlerd. spring-tx包下的
http/://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandlere. spring-jdbc包下的
http/://www.springframework.org/schema/jdbc=org.springframework.jdbc.config.JdbcNamespaceHandlere. spring-webmvc包下的
http/://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandlerspring.handlers文件中=號左邊的是命名空間uri,=號右邊的是命名空間處理器類的全名稱。
我們看看p命名空間的處理器SimplePropertyNamespaceHandler類的源碼,如下。
public class SimplePropertyNamespaceHandler implements NamespaceHandler { private static final String REF_SUFFIX = "-ref"; @Override public void init() { } @Override public BeanDefinition parse(Element element, ParserContext parserContext) { parserContext.getReaderContext().error( "Class [" + getClass().getName() + "] does not support custom elements.", element); return null; } @Override public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext) { // p命名空間只處理屬性 if (node instanceof Attr) { Attr attr = (Attr) node; String propertyName = parserContext.getDelegate().getLocalName(attr); String propertyValue = attr.getValue(); // 獲取BeanDefinition的MutablePropertyValues對象 MutablePropertyValues pvs = definition.getBeanDefinition().getPropertyValues(); if (pvs.contains(propertyName)) { parserContext.getReaderContext().error("Property '" + propertyName + "' is already defined using " + "both <property> and inline syntax. Only one approach may be used per property.", attr); } if (propertyName.endsWith(REF_SUFFIX)) { // 獲取屬性名稱 propertyName = propertyName.substring(0, propertyName.length() - REF_SUFFIX.length()); // 設置為bean引用 pvs.add(Conventions.attributeNameToPropertyName(propertyName), new RuntimeBeanReference(propertyValue)); } else { pvs.add(Conventions.attributeNameToPropertyName(propertyName), propertyValue); } } return definition; }}再看看aop命名空間的處理器AopNamespaceHandler 類的源碼,如下。
public class AopNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { // 注冊config標簽的的解析器 registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser()); // 注冊aspectj-autoproxy標簽的解析器 registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser()); // 注冊scoped-proxy標簽的裝飾器 registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator()); // 注冊spring-configured標簽的解析器 registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); }}上面兩個例子是spring創建NamespaceHandler實現類的兩種方式,第一種是直接實現NamespaceHandler接口提供的方法,這種方式適合于命名空間中標簽只有一個或者解析標簽和屬性的過程很簡單,比如p命名空間;第二種是繼承抽象類NamespaceHandlerSupport 并實現init方法,在init方法中注冊標簽的解析器和裝飾器以及屬性的裝飾器,spring中大多數命名空間處理器都使用這種方式。
NamespaceHandlerSupport向子類提供了三個方法分別用于注冊標簽的解析、標簽的裝飾器、屬性的裝飾器,如下。
/** * 注冊標簽的解析器BeanDefinitionParser對象 */ protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) { this.parsers.put(elementName, parser); } /** * 注冊標簽的裝飾器BeanDefinitionDecorator對象 */ protected final void registerBeanDefinitionDecorator(String elementName, BeanDefinitionDecorator dec) { this.decorators.put(elementName, dec); } /** * 注冊屬性的裝飾器BeanDefinitionDecorator對象 */ protected final void registerBeanDefinitionDecoratorForAttribute(String attrName, BeanDefinitionDecorator dec) { this.attributeDecorators.put(attrName, dec); }spring為解析標簽自定義了一個BeanDefinitionParser接口,源碼如下。
public interface BeanDefinitionParser { /** * 解析指定的節點元素,并返回一個已經注冊到BeanDefinitionRegistry對象(BeanDefinition注冊表)中的BeanDefinition對象。 */ BeanDefinition parse(Element element, ParserContext parserContext);}spring還為裝飾標簽和屬性定義了一個BeanDefinitionDecorator接口,源碼如下。
public interface BeanDefinitionDecorator { /** * 根據指定Node對象裝飾BeanDefinitionHolder對象,這個Node對象可以是屬性,即Attr對象, * 也可以是標簽元素,即Element對象 * 最后返回一個裝飾好了的BeanDefinitionHolder 對象 */ BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder definition, ParserContext parserContext);}看到這里,我們再來說說NamespaceHandler和BeanDefinitionParser 以及BeanDefinitionDecorator二者的關系。 a. 對NamespaceHandler來說,它們可有可無。不過對于spring框架本身來說,這樣的設計提高了spring的可維護性、可擴展性和易讀性。 b. 對BeanDefinitionParser 和BeanDefinitionDecorator來說,它們必須受NamespaceHandler的管控和調度,否則它們將一無是處。
(1)spring通過NamespaceHandler對象來解析自定義標簽和屬性,同時也用這個對象來裝飾BeanDefinitionHolder對象。
(2)DefaultNamespaceHandlerResolver用于管理NamespaceHandler實現類。因此可以從DefaultNamespaceHandlerResolver中根據命名空間uri獲取到對應的NamespaceHandler對象。
(3)NamespaceHandler實現類需要在/META-INF/spring.handler文件中注冊,否則不能被DefaultNamespaceHandlerResolver對象加載。
(4)命名空間中有多個標簽需要解析和裝飾,命名空間處理器類最好繼承NamespaceHandlerSupport ,并為標簽分別定義BeanDefinitionParser解析器或者BeanDefinitionDecorator裝飾器。
新聞熱點
疑難解答