spring的applicationContext.xml文件與NamespaceHandler解析
applicationContext.xml文件解析
- Spring容器啟動,在創(chuàng)建BeanFactory時,需要加載和解析當前ApplicationContext對應的配置文件applicationContext.xml,從而獲取bean相關的配置信息。
- 在內(nèi)部實現(xiàn)的調(diào)用關系為: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ù),調(diào)用DefaultBeanDefinitionDocumentReader的registerBeanDefinitions,在registerBeanDefinitions中解析XML文件的內(nèi)容,從中獲取并生成beanDefinitions;
- registerBeanDefinitions方法:從XML文件對應的Document對象獲取root Element,從root Element一直往下解析所有的xml標簽Element;
- 針對每個xml標簽Element,解析過程為:新增一個與該Element對應的BeanDefinitionParserDelegate,根據(jù)該Element的命名空間nameSpaceUri,從NamespaceHandlerResolver獲取該nameSpaceUri對應的NamespaceHandler;
- 其中NamespaceHandler內(nèi)部維護了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內(nèi)部是通過BeanDefinitionParser接口來定義的。
NamespaceHandler
- 在Spring的設計當中,通常每個標簽都屬于一個特定的名稱空間來避免名稱沖突,如context名稱空間,mvc名稱空間等,每個名稱空間內(nèi)部可定義多個標簽。
- 使用NamespaceHandler接口來維護每個名稱空間內(nèi)部的標簽 和標簽處理器之間的映射關系。這樣XmlBeanDefinitionReader在解析applicationContext.xml文件時,遇到某個名稱空間,則獲取這名稱空間對應的NamespaceHandler,然后通過NamespaceHandler進一步獲取內(nèi)部標簽對應的標簽處理器。
Spring內(nèi)部的使用和源碼實現(xiàn)
- 在spring-context,spring-webmvc,spring-beans等spring框架子項目的META-INF/spring.handlers文件中,定義標簽命名空間與NamespaceHandler實現(xiàn)類之間的映射關系。
- 如下為spring-webmvc子項目的META-INF/spring.handlers文件內(nèi)容:
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元素時調(diào)用,如下:
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的初始化,使用的是懶加載機制,即當調(diào)用了NamespaceHandlerResolver的resolve方法時,才進行加載,加載之后進行緩存。如下:
NamespaceHandler的初始化:在DefaultNamespaceHandlerResolver的getHandlerMappings方法實現(xiàn)。
在resolve方法中調(diào)用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;
}
// 不是第一次調(diào)用,則已經(jīng)是NamespaceHandler類型了,可以直接返回
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
// 第一次調(diào)用,由上面分析可知:
// 剛開始從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);
// 調(diào)用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解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
如何使用SpringMVC的消息轉(zhuǎn)換器設置日期格式
這篇文章主要介紹了如何使用SpringMVC的消息轉(zhuǎn)換器設置日期格式問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07
SpringBoot實現(xiàn)網(wǎng)站的登陸注冊邏輯記錄
登陸注冊功能是我們?nèi)粘i_發(fā)中經(jīng)常遇到的一個功能,下面這篇文章主要給大家介紹了關于SpringBoot實現(xiàn)網(wǎng)站的登陸注冊邏輯的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2021-10-10
Java Builder模式實現(xiàn)原理及優(yōu)缺點解析
這篇文章主要介紹了Java Builder模式實現(xiàn)原理及優(yōu)缺點解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-10-10
Java實現(xiàn)日志文件監(jiān)聽并讀取相關數(shù)據(jù)的方法實踐
本文主要介紹了Java實現(xiàn)日志文件監(jiān)聽并讀取相關數(shù)據(jù)的方法實踐,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05
StringUtils,CollectionUtils判斷為空的方法和原生代碼哪個效率最高
這篇文章主要介紹了StringUtils,CollectionUtils判斷為空的方法和原生代碼哪個效率最高,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02

