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解析内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringCloud将Nacos作为配置中心实现流程详解

    SpringCloud将Nacos作为配置中心实现流程详解

    这篇文章主要介绍了Springcloud中的Nacos Config服务配置,本文以用户微服务为例,进行统一的配置,结合实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-10-10
  • Java实现为图片添加水印功能

    Java实现为图片添加水印功能

    在图像处理领域,水印是一种常见的保护版权和标识图片归属的方法,Java提供了强大的图像处理能力,可以通过Graphics2D类在图像上绘制水印,下面我们来看看具体操作步骤吧
    2025-02-02
  • Java设计模式之享元模式

    Java设计模式之享元模式

    这篇文章主要为大家详细介绍了Java设计模式之享元模式的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-08-08
  • 使用Java实现Redis限流的方法

    使用Java实现Redis限流的方法

    限流的作用是防止某个段时间段内的请求数过多,造成模块因高并发而不可用。这篇文章给大家介绍使用Java实现Redis限流的相关知识,一起看看吧
    2021-09-09
  • java并发包JUC同步器框架AQS框架原文翻译

    java并发包JUC同步器框架AQS框架原文翻译

    发现了一篇JDK作者的论文《The java.util.concurrent Synchronizer Framework》主要描述了作者对AbstractQueuedSynchronizer同步器框架的设计和实现。权威性毋庸置疑!自然需要拜读一下,配上中文翻译,希望大家能有所收获
    2022-02-02
  • Java调用HTTPS接口的两种方式及完整代码

    Java调用HTTPS接口的两种方式及完整代码

    HTTPS是以安全为目标的HTTP通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性 ,HTTPS在HTTP的基础下加SSL,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL,本文给大家介绍了Java调用https接口的两种方式及完整代码,需要的朋友可以参考下
    2025-05-05
  • Java程序图形用户界面设计之容器JFrame

    Java程序图形用户界面设计之容器JFrame

    图形界面(简称GUI)是指采用图形方式显示的计算机操作用户界面。与早期计算机使用的命令行界面相比,图形界面对于用户来说在视觉上更易于接受,本篇精讲Java语言中关于图形用户界面的基本容器JFrame
    2022-02-02
  • SpringBoot如何使用@Cacheable进行缓存与取值

    SpringBoot如何使用@Cacheable进行缓存与取值

    这篇文章主要介绍了SpringBoot如何使用@Cacheable进行缓存与取值,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • Java实现压缩图片大小

    Java实现压缩图片大小

    这篇文章主要为大家详细介绍了Java实现压缩图片大小,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • Request对象如何获取请求头数据

    Request对象如何获取请求头数据

    这篇文章主要介绍了Request对象如何获取请求头数据问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07

最新评论