spring的applicationContext.xml文件與NamespaceHandler解析
applicationContext.xml文件解析
- Spring容器啟動,在創(chuàng)建BeanFactory時,需要加載和解析當前ApplicationContext對應的配置文件applicationContext.xml,從而獲取bean相關的配置信息。
- 在內部實現(xiàn)的調用關系為:ApplicationContext通過XmlBeanDefinitionReader來完成從applicationContext.xml獲取應用配置的bean信息,并注冊到關聯(lián)的BeanFactory中。XmlBeanDefinitionReader的主要工作為解析xml文件的標簽,包括從bean標簽直接創(chuàng)建BeanDefinition,以及創(chuàng)建用于處理context:component-scan標簽的BeanFactoryPostProcessor,然后間接創(chuàng)建BeanDefinitions。
- 整個解析過程如下:
- XmlBeanDefinitionReader創(chuàng)建DefaultBeanDefinitionDocumentReader對象實例,讀取XML文件并封裝成Document對象,然后將該Document對象作為參數(shù),調用DefaultBeanDefinitionDocumentReader的registerBeanDefinitions,在registerBeanDefinitions中解析XML文件的內容,從中獲取并生成beanDefinitions;
- registerBeanDefinitions方法:從XML文件對應的Document對象獲取root Element,從root Element一直往下解析所有的xml標簽Element;
- 針對每個xml標簽Element,解析過程為:新增一個與該Element對應的BeanDefinitionParserDelegate,根據(jù)該Element的命名空間nameSpaceUri,從NamespaceHandlerResolver獲取該nameSpaceUri對應的NamespaceHandler;
- 其中NamespaceHandler內部維護了xml標簽和xml標簽解析器BeanDefinitionParser的映射,所以由NamespaceHandler獲取當前正在處理的xml標簽Element對應的標簽處理器BeanDefinitionParser(如< bean …>標簽處理器則直接創(chuàng)建BeanDefinition對象,而< component-scan …>標簽則是創(chuàng)建一個BeanFactoryPostProcessor,之后由該BeanFactory后置處理器通過掃描指定包獲取類并生成BeanDefinition注冊到BeanFactory),由該BeanDefinitionParser從xml標簽Element生成注冊到BeanFactory的BeanDefinition,或者生成BeanFactoryPostProcessor。
- 所以這里就需要定義每個標簽對應一個特定的標簽解析器了,在spring內部是通過BeanDefinitionParser接口來定義的。
NamespaceHandler
- 在Spring的設計當中,通常每個標簽都屬于一個特定的名稱空間來避免名稱沖突,如context名稱空間,mvc名稱空間等,每個名稱空間內部可定義多個標簽。
- 使用NamespaceHandler接口來維護每個名稱空間內部的標簽 和標簽處理器之間的映射關系。這樣XmlBeanDefinitionReader在解析applicationContext.xml文件時,遇到某個名稱空間,則獲取這名稱空間對應的NamespaceHandler,然后通過NamespaceHandler進一步獲取內部標簽對應的標簽處理器。
Spring內部的使用和源碼實現(xiàn)
- 在spring-context,spring-webmvc,spring-beans等spring框架子項目的META-INF/spring.handlers文件中,定義標簽命名空間與NamespaceHandler實現(xiàn)類之間的映射關系。
- 如下為spring-webmvc子項目的META-INF/spring.handlers文件內容:
http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
對應的NamespaceHandler的實現(xiàn)類為:
package org.springframework.web.servlet.config; /** * {@link NamespaceHandler} for Spring MVC configuration namespace. * * @author Keith Donald * @author Jeremy Grelle * @author Sebastien Deleuze * @since 3.0 */ public class MvcNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser()); registerBeanDefinitionParser("default-servlet-handler", new DefaultServletHandlerBeanDefinitionParser()); registerBeanDefinitionParser("interceptors", new InterceptorsBeanDefinitionParser()); registerBeanDefinitionParser("resources", new ResourcesBeanDefinitionParser()); registerBeanDefinitionParser("view-controller", new ViewControllerBeanDefinitionParser()); registerBeanDefinitionParser("redirect-view-controller", new ViewControllerBeanDefinitionParser()); registerBeanDefinitionParser("status-controller", new ViewControllerBeanDefinitionParser()); registerBeanDefinitionParser("view-resolvers", new ViewResolversBeanDefinitionParser()); registerBeanDefinitionParser("tiles-configurer", new TilesConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("freemarker-configurer", new FreeMarkerConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser()); registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser()); } }
- XmlBeanDefinitionReader主要是從applicationContext.xml創(chuàng)建一個Document對象,交給子組件BeanDefinitionDocumentReader處理這個Document對象。而NamespaceHandler的實現(xiàn)類主要是在BeanDefinitionDocumentReader中在解析Document對象的Element元素時調用,如下:
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { String namespaceUri = getNamespaceURI(ele); if (namespaceUri == null) { return null; } // 獲取一個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 // 在它里面維護的parser集合中找到 // 與該標簽對應的parser,由該parser來執(zhí)行解析 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
- 以上已經(jīng)說明了通過XmlBeanDefinitionReader的NamespaceHandlerResolver維護:名稱空間和NamespaceHandler的映射;通過NamespaceHandler來維護標簽和Parser的映射,那么兩者是什么時候初始化注冊的呢?
- 其實NamespaceHandler和Parser的初始化,使用的是懶加載機制,即當調用了NamespaceHandlerResolver的resolve方法時,才進行加載,加載之后進行緩存。如下:
NamespaceHandler的初始化:在DefaultNamespaceHandlerResolver的getHandlerMappings方法實現(xiàn)。
在resolve方法中調用getHandlerMappings方法。
/** * Load the specified NamespaceHandler mappings lazily. */ private Map<String, Object> getHandlerMappings() { Map<String, Object> handlerMappings = this.handlerMappings; if (handlerMappings == null) { synchronized (this) { handlerMappings = this.handlerMappings; if (handlerMappings == null) { if (logger.isTraceEnabled()) { logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]"); } try { Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); if (logger.isTraceEnabled()) { logger.trace("Loaded NamespaceHandler mappings: " + mappings); } handlerMappings = new ConcurrentHashMap<>(mappings.size()); CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); this.handlerMappings = handlerMappings; } catch (IOException ex) { throw new IllegalStateException( "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex); } } } } return handlerMappings; }
- 從META-INF/spring.handlers文件加載鍵值對,并緩存在類型為ConcurrentHashMap的handlerMappings中;
- 注意這里并沒有初始化NamespaceHandler,即handlerMappings的value還是String類型。
NamespaceHandler包含的parsers的初始化:在resolve方法中進行懶加載初始化。
/** * Locate the {@link NamespaceHandler} for the supplied namespace URI * from the configured mappings. * @param namespaceUri the relevant namespace URI * @return the located {@link NamespaceHandler}, or {@code null} if none found */ @Override @Nullable public NamespaceHandler resolve(String namespaceUri) { // 懶加載NamespaceHandler的handlerMappings Map<String, Object> handlerMappings = getHandlerMappings(); Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } // 不是第一次調用,則已經(jīng)是NamespaceHandler類型了,可以直接返回 else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler) handlerOrClassName; } // 第一次調用,由上面分析可知: // 剛開始從META-INF/spring.handlers // 讀出時,handlerMappings的value是字符串 else { String className = (String) handlerOrClassName; try { // 加載NamespaceHandler Class<?> handlerClass = ClassUtils.forName(className, this.classLoader); if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) { throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface"); } NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); // 調用init方法完成parsers的初始化 namespaceHandler.init(); handlerMappings.put(namespaceUri, namespaceHandler); return namespaceHandler; } catch (ClassNotFoundException ex) { throw new FatalBeanException("Could not find NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", ex); } catch (LinkageError err) { throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]", err); } } } protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) { this.parsers.put(elementName, parser); }
NamespaceHandler的init方法實現(xiàn):各個NamespaceHandler接口實現(xiàn)類,在init方法中注冊xml的標簽和Parser之間的映射關系:如下為context標簽的名稱空間處理器ContextNamespaceHandler:
/** * {@link org.springframework.beans.factory.xml.NamespaceHandler} * for the '{@code context}' namespace. * * @author Mark Fisher * @author Juergen Hoeller * @since 2.5 */ public class ContextNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser()); registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser()); registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser()); // <context:component-scan /> registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser()); registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser()); registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser()); } }
插件機制拓展支持
如果需要開發(fā)一個插件并自定義標簽,然后融入到Spring容器中,則可以在自身插件項目中,基于NamespaceHandler來實現(xiàn),如在Dubbo項目中,就是利用了Spring的這個機制來容器到Spring框架的
基本步驟包括:
- 自定義xsd標簽定義,并添加到插件項目的META-INF目錄中,路徑類似于一個普通pacakage下面的一個類;
- 實現(xiàn)BeanDefinitionParser接口,定義標簽的處理邏輯;
- 實現(xiàn)NamespaceHandler接口,一般繼承Spring的NamespaceHandlerSupport即可。在init方法中,使用registerBeanDefinitionParser方法配置標簽名稱和BeanDefinitionParser實現(xiàn)類的映射關系。
- 在META-INF/spring.schemas文件中,定義xsd文件全限定名與一個在applicationContext.xml文件中可以配置的命名空間url的映射;
- 在META-INF/spring.handlers文件中,定義以上命名空間url和該NamespaceHandler實現(xiàn)類的映射。
到此這篇關于spring的applicationContext.xml文件與NamespaceHandler解析的文章就介紹到這了,更多相關applicationContext與NamespaceHandler解析內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot實現(xiàn)網(wǎng)站的登陸注冊邏輯記錄
登陸注冊功能是我們日常開發(fā)中經(jīng)常遇到的一個功能,下面這篇文章主要給大家介紹了關于SpringBoot實現(xiàn)網(wǎng)站的登陸注冊邏輯的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2021-10-10Java Builder模式實現(xiàn)原理及優(yōu)缺點解析
這篇文章主要介紹了Java Builder模式實現(xiàn)原理及優(yōu)缺點解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-10-10Java實現(xiàn)日志文件監(jiān)聽并讀取相關數(shù)據(jù)的方法實踐
本文主要介紹了Java實現(xiàn)日志文件監(jiān)聽并讀取相關數(shù)據(jù)的方法實踐,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05StringUtils,CollectionUtils判斷為空的方法和原生代碼哪個效率最高
這篇文章主要介紹了StringUtils,CollectionUtils判斷為空的方法和原生代碼哪個效率最高,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02