spring的applicationContext.xml文件与NamespaceHandler解析

 更新时间:2023年12月02日 09:52:32   作者:服务端开发  
这篇文章主要介绍了spring的applicationContext.xml文件与NamespaceHandler解析,Spring容器启动,在创建BeanFactory时,需要加载和解析当前ApplicationContext对应的配置文件applicationContext.xml,从而获取bean相关的配置信息,需要的朋友可以参考下

applicationContext.xml文件解析

  • Spring容器启动,在创建BeanFactory时,需要加载和解析当前ApplicationContext对应的配置文件applicationContext.xml,从而获取bean相关的配置信息。
  • 在内部实现的调用关系为:ApplicationContext通过XmlBeanDefinitionReader来完成从applicationContext.xml获取应用配置的bean信息,并注册到关联的BeanFactory中。XmlBeanDefinitionReader的主要工作为解析xml文件的标签,包括从bean标签直接创建BeanDefinition,以及创建用于处理context:component-scan标签的BeanFactoryPostProcessor,然后间接创建BeanDefinitions。
  • 整个解析过程如下:
    • XmlBeanDefinitionReader创建DefaultBeanDefinitionDocumentReader对象实例,读取XML文件并封装成Document对象,然后将该Document对象作为参数,调用DefaultBeanDefinitionDocumentReader的registerBeanDefinitions,在registerBeanDefinitions中解析XML文件的内容,从中获取并生成beanDefinitions;
    • registerBeanDefinitions方法:从XML文件对应的Document对象获取root Element,从root Element一直往下解析所有的xml标签Element;
    • 针对每个xml标签Element,解析过程为:新增一个与该Element对应的BeanDefinitionParserDelegate,根据该Element的命名空间nameSpaceUri,从NamespaceHandlerResolver获取该nameSpaceUri对应的NamespaceHandler;
    • 其中NamespaceHandler内部维护了xml标签和xml标签解析器BeanDefinitionParser的映射,所以由NamespaceHandler获取当前正在处理的xml标签Element对应的标签处理器BeanDefinitionParser(如< bean …>标签处理器则直接创建BeanDefinition对象,而< component-scan …>标签则是创建一个BeanFactoryPostProcessor,之后由该BeanFactory后置处理器通过扫描指定包获取类并生成BeanDefinition注册到BeanFactory),由该BeanDefinitionParser从xml标签Element生成注册到BeanFactory的BeanDefinition,或者生成BeanFactoryPostProcessor。
    • 所以这里就需要定义每个标签对应一个特定的标签解析器了,在spring内部是通过BeanDefinitionParser接口来定义的。

NamespaceHandler

  • 在Spring的设计当中,通常每个标签都属于一个特定的名称空间来避免名称冲突,如context名称空间,mvc名称空间等,每个名称空间内部可定义多个标签。
  • 使用NamespaceHandler接口来维护每个名称空间内部的标签 和标签处理器之间的映射关系。这样XmlBeanDefinitionReader在解析applicationContext.xml文件时,遇到某个名称空间,则获取这名称空间对应的NamespaceHandler,然后通过NamespaceHandler进一步获取内部标签对应的标签处理器。

Spring内部的使用和源码实现

  • 在spring-context,spring-webmvc,spring-beans等spring框架子项目的META-INF/spring.handlers文件中,定义标签命名空间与NamespaceHandler实现类之间的映射关系。
  • 如下为spring-webmvc子项目的META-INF/spring.handlers文件内容:
http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler

对应的NamespaceHandler的实现类为:

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创建一个Document对象,交给子组件BeanDefinitionDocumentReader处理这个Document对象。而NamespaceHandler的实现类主要是在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来执行解析
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
  • 以上已经说明了通过XmlBeanDefinitionReader的NamespaceHandlerResolver维护:名称空间和NamespaceHandler的映射;通过NamespaceHandler来维护标签和Parser的映射,那么两者是什么时候初始化注册的呢?
  • 其实NamespaceHandler和Parser的初始化,使用的是懒加载机制,即当调用了NamespaceHandlerResolver的resolve方法时,才进行加载,加载之后进行缓存。如下:

NamespaceHandler的初始化:在DefaultNamespaceHandlerResolver的getHandlerMappings方法实现。

在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;
}
  1. 从META-INF/spring.handlers文件加载键值对,并缓存在类型为ConcurrentHashMap的handlerMappings中;
  2. 注意这里并没有初始化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;
  	}
  	// 不是第一次调用,则已经是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方法实现:各个NamespaceHandler接口实现类,在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());
	}
}

插件机制拓展支持

如果需要开发一个插件并自定义标签,然后融入到Spring容器中,则可以在自身插件项目中,基于NamespaceHandler来实现,如在Dubbo项目中,就是利用了Spring的这个机制来容器到Spring框架的

基本步骤包括:

  1. 自定义xsd标签定义,并添加到插件项目的META-INF目录中,路径类似于一个普通pacakage下面的一个类;
  2. 实现BeanDefinitionParser接口,定义标签的处理逻辑;
  3. 实现NamespaceHandler接口,一般继承Spring的NamespaceHandlerSupport即可。在init方法中,使用registerBeanDefinitionParser方法配置标签名称和BeanDefinitionParser实现类的映射关系。
  4. 在META-INF/spring.schemas文件中,定义xsd文件全限定名与一个在applicationContext.xml文件中可以配置的命名空间url的映射;
  5. 在META-INF/spring.handlers文件中,定义以上命名空间url和该NamespaceHandler实现类的映射。

到此这篇关于spring的applicationContext.xml文件与NamespaceHandler解析的文章就介绍到这了,更多相关applicationContext与NamespaceHandler解析内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring Cloud Eureka: 指定Zone方式

    Spring Cloud Eureka: 指定Zone方式

    这篇文章主要介绍了Spring Cloud Eureka: 指定Zone方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Spring Boot面试必问之启动流程知识点详解

    Spring Boot面试必问之启动流程知识点详解

    SpringBoot是Spring开源组织下的子项目,是Spring组件一站式解决方案,主要是简化了使用Spring的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手,这篇文章主要给大家介绍了关于Spring Boot面试必问之启动流程知识点的相关资料,需要的朋友可以参考下
    2022-06-06
  • Mybatis-Plus中分页插件PaginationInterceptor的使用

    Mybatis-Plus中分页插件PaginationInterceptor的使用

    我们在开发的过程中,经常会遇到分页操作,本文主要介绍了Mybatis-Plus中分页插件PaginationInterceptor的使用,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • mybatis模糊查询、分页和别名配置的方法

    mybatis模糊查询、分页和别名配置的方法

    这篇文章主要介绍了mybatis模糊查询、分页和别名配置,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • 浅谈一下Spring的核心基础IOC与DI

    浅谈一下Spring的核心基础IOC与DI

    这篇文章主要介绍了Spring的核心基础IOC与DI的详细用法,spring技术是现在企业开发中几乎必备的技术选型,那么学好spring就很重要,本篇着重讲解spring的核心机制,IOD与DI,一起来看看吧
    2023-03-03
  • Java lock同步锁使用实例解析

    Java lock同步锁使用实例解析

    这篇文章主要介绍了Java lock同步锁使用实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • Spring boot集成redis lettuce代码实例

    Spring boot集成redis lettuce代码实例

    这篇文章主要介绍了Spring boot集成redis lettuce代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • java中的除法运算和取模运算解读

    java中的除法运算和取模运算解读

    这篇文章主要介绍了java中的除法运算和取模运算,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • stream中使用peek一些陷阱避免及解决方法

    stream中使用peek一些陷阱避免及解决方法

    这篇文章主要为大家介绍了stream中使用peek一些陷阱避免及解决方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • Java不借助第三变量实现两数交换的示例

    Java不借助第三变量实现两数交换的示例

    本文主要介绍了Java不借助第三变量实现两数交换的示例,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02

最新评论