百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

spring源码系列之xml解析

xsobi 2024-11-24 23:34 1 浏览

目录

  • 1. 读源码的方法
    • 1.1 重视官方英文文档
    • 1.2 写示例程序debug源码
    • 1.3 抓大放小
    • 1.4 写源码注释、画流程图
    • 1.5 思考背后的设计思想
    • 1.6 螺旋式学习
  • 2. xml文件解析
    • 2.1 流程概览
    • 2.2 源码分析
  • 3. 总结



回到顶部

1. 读源码的方法

java程序员都知道读源码的重要性,尤其是spring的源码,代码设计不仅优雅,而且功能越来越强大,几乎可以与很多开源框架整合,让应用更易于专注业务领域开发。但是能把spring的源码吃透,不仅需要花费大量时间与精力,更需要需要掌握一些方法。下面结合自己读源码与走过的一些弯路,结合网上知名博客专家的建议,整理出以下要点,与读者共勉。

1.1 重视官方英文文档

spring的官方文档写得非常全面,基本可以认为是spring源码思想的一手来源,上面有很多例子不仅帮助读者如何应用,更能帮助我们了解其背后的思想,官方也用大量描述性的文字进行了相关思想的解读,让读者从一个总览上看大致了解spring的核心功能与特性。截止到目前,官方的最新版本是5.3.6,地址如下:
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core。

1.2 写示例程序debug源码

通过简单的示例程序,找到源码的入口,通过debug而非仅仅看静态的源码,只有看到真实跑起来的程序以及运行时的值时,心里才大致清楚源码做了哪些事情。

1.3 抓大放小

抓住源码主要流程,而非陷入细节,如果一开始就抠细节,不仅会打消看源码的积极性,也理不清主要流程,最后只能半途而废。只有主流程非常熟悉的情况下,并有时间精力,有兴趣可以深究一些自己感兴趣的细节。

1.4 写源码注释、画流程图

源码的一些重要方法与主要流程可以通过写注释、画流程图来加深理解。

1.5 思考背后的设计思想

源码之所以经典,是因为设计思想优秀,spring的源码在设计模式的灵活应用、类的抽象与封装、框架的可扩展性都做到了极致,可以把该思想以及实践应用到自己的项目设计里面。

1.6 螺旋式学习

任何知识都是循序渐进,源码学习更是如此,因为源码很容易让人半途而废,只有通过刻意重复来逐步提升,每一次都不求甚解,能搞懂多少就搞懂多少,但是每一次都比上一次的理解提升一点,也可参考优质博客系列对比学习,最终将源码的精髓吃透。

回到顶部

2. xml文件解析

2.1 流程概览

上图描述了xml的解析的主要流程,大致分为三个步骤:
step1: 创建BeanFactory对象;
step2: 解析xml标签,默认标签如beanimport,自定义标签<context:component-scan base-package=xxx>,把标签封装成BeanDefinition对象;
step3: 最后通过注册机BeanDefinitionRegistry注册到BeanFactory的实现类DefaultListableBeanFactory

2.2 源码分析

AbstractApplicationContext类中最重要的方法refresh(),里面调用很多方法,本文先重点看xml解析相关的方法。

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {

		        ......

			/*
			  1、创建BeanFactory对象
			* 2、xml解析
			* 	默认标签解析:bean、import等
			* 	自定义标签解析 如:<context:component-scan base-package="com.xxx"/>
			* 	自定义标签解析流程:
			* 		a、根据当前解析标签的头信息找到对应的namespaceUri
			* 		b、加载spring所以jar中的spring.handlers文件。并建立映射关系
			* 		c、根据namespaceUri从映射关系中找到对应的实现了NamespaceHandler接口的类
			* 		d、调用类的init方法,init方法是注册了各种自定义标签的解析类
			* 		e、根据namespaceUri找到对应的解析类,然后调用paser方法完成标签解析
			*
			* 3、把解析出来的xml标签封装成BeanDefinition对象
			* */
			// 告诉子类刷新内部beanFactory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
                        ......
}

进入obtainFreshBeanFactory()方法,到refreshBeanFactory(),该方法是个抽象方法,由具体子类实现。

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		// 刷新beanFactory
		refreshBeanFactory();
                // 获取beanFactory
		return getBeanFactory();
}

子类AbstractRefreshableApplicationContext实现refreshBeanFactory()方法;

protected final void refreshBeanFactory() throws BeansException {
		//如果BeanFactory不为空,则清除BeanFactory和里面的实例
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			//创建beanFactory
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			//传入beanFactory,并装载BeanDefinition对象
			loadBeanDefinitions(beanFactory);
			this.beanFactory = beanFactory;
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
}

上面方法主要做了两件事,一是创建beanFactory,二是beanFactory作为参数传入,并负责装载BeanDefinition对象。接下来进入loadBeanDefinitions(beanFactory)方法,该方法是个抽象方法,交给子类AbstractXmlApplicationContext去实现,子类方法如下。

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		//为给定的BeanFactory创建xml的解析器,这里是一个委托模式
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		beanDefinitionReader.setEnvironment(this.getEnvironment());
		//这里传一个this进去,因为ApplicationContext是实现了ResourceLoader接口的
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		//传入解析器,装载BeanDefinitions
		loadBeanDefinitions(beanDefinitionReader);
	}

上述方法创建xml解析器XmlBeanDefinitionReader,并交由解析器完成BeanDefinitions的装载。再次进入AbstractXmlApplicationContext类的loadBeanDefinitions方法。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		//获取需要加载的xml配置文件
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
                        // xml解析器完成装载
			reader.loadBeanDefinitions(configLocations);
		}
	}

再次进入xml解析器的loadBeanDefinitions方法,

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
      ......		
      //把字符串类型的xml文件路径,转换成Resource对象
      Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
      // 传入Resource对象装载BeanDefinitions
      int count = loadBeanDefinitions(resources);
      ......

进入上述的loadBeanDefinitions(resources)方法;

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
			//把inputSource 封装成Document文件对象
			Document doc = doLoadDocument(inputSource, resource);

			//根据document对象,去注册BeanDefinitions
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}

上述方法一是封装Document文件对象,二是用Document对象去注册BeanDefinitions,随后进入registerBeanDefinitions方法;

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		//创建BeanDefinitionDocumentReader
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();

		//并委托BeanDefinitionDocumentReader这个类进行document的解析
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
}

该方法创建BeanDefinitionDocumentReader对象,并委托其解析document,进入registerBeanDefinitions方法。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		if (delegate.isDefaultNamespace(root)) {
			NodeList nl = root.getChildNodes();
			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);
		}
}

上述方法主要完成默认标签解析,与自定义标签解析,默认标签如同importaliasbeanbeans,自定义标签比如<aop:aspectj-autoproxy />,默认标签重点分析bean标签的解析;

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		        //bean签解析
			BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				//完成document到BeanDefinition对象转换后,对BeanDefinition对象进行缓存注册
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			        ......
		}
	}

上述方法主要是bean标签的解析,最后对BeanDefinition对象进行缓存注册,先分析解析;

public AbstractBeanDefinition parseBeanDefinitionElement(
			Element ele, String beanName, @Nullable BeanDefinition containingBean) {

		        ......
                        //创建BeanDefinition
			AbstractBeanDefinition bd = createBeanDefinition(className, parent);
                        //解析BeanDefinition里面的属性
			parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
			bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
                        //解析meta元素
			parseMetaElements(ele, bd);
                        //解析Lookup方法
			parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
                        //解析ReplacedMethod方法
			parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
                        //解析构造参数方法
			parseConstructorArgElements(ele, bd);
                        //解析属性元素方法
			parsePropertyElements(ele, bd);
                        //解析Qualifier元素方法
			parseQualifierElements(ele, bd);

			bd.setResource(this.readerContext.getResource());
			bd.setSource(extractSource(ele));
                        // 最后返回beanDefinition
                        return bd;

再分析注册BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()),进入该方法,最后会进入DefaultListableBeanFactory类,

@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {

		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}

		//先判断BeanDefinition是否已经注册
		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
		if (existingDefinition != null) {
			if (!isAllowBeanDefinitionOverriding()) {
				throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
			}
			else if (existingDefinition.getRole() < beanDefinition.getRole()) {
				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
				if (logger.isInfoEnabled()) {
					logger.info("Overriding user-defined bean definition for bean '" + beanName +
							"' with a framework-generated bean definition: replacing [" +
							existingDefinition + "] with [" + beanDefinition + "]");
				}
			}
			else if (!beanDefinition.equals(existingDefinition)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Overriding bean definition for bean '" + beanName +
							"' with a different definition: replacing [" + existingDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			else {
				if (logger.isTraceEnabled()) {
					logger.trace("Overriding bean definition for bean '" + beanName +
							"' with an equivalent definition: replacing [" + existingDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			if (hasBeanCreationStarted()) {
				// Cannot modify startup-time collection elements anymore (for stable iteration)
				synchronized (this.beanDefinitionMap) {
					this.beanDefinitionMap.put(beanName, beanDefinition);
					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					removeManualSingletonName(beanName);
				}
			}
			else {
				//把beanDefinition缓存到map中
				this.beanDefinitionMap.put(beanName, beanDefinition);
				//把beanName放到beanDefinitionNames list中
				this.beanDefinitionNames.add(beanName);
				removeManualSingletonName(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}

最终看出所谓的注册到BeanFactory的容器中的类,无非就是一个定义了ConcurrentHashMap类型的beanDefinitionMap

自定义标签解析BeanDefinitionParserDelegate类,执行parseCustomElement方法;

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
                // 获取namespaceURI
		String namespaceUri = getNamespaceURI(ele);
		if (namespaceUri == null) {
			return null;
		}
                // 解析namespaceURI对应的handler类
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
                // 执行handler的解析方法
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}

上述过程主要完成以下步骤:
step1:获取namespaceURI;
step2:解析namespaceURI对应的handler类;
step3:执行handler方法解析。
其中step2又分为几个步骤,代码进入如下方法

public NamespaceHandler resolve(String namespaceUri) {
                // 从spring.handler里面获取映射关系
		Map<String, Object> handlerMappings = getHandlerMappings();
                // 根据namespaceURI从映射关系map中获取对应的处理类handler
		Object handlerOrClassName = handlerMappings.get(namespaceUri);
		if (handlerOrClassName == null) {
			return null;
		}
		else if (handlerOrClassName instanceof NamespaceHandler) {
			return (NamespaceHandler) handlerOrClassName;
		}
		else {
                        // 通过反射实例化namespaceHandler类
			String className = (String) handlerOrClassName;
			try {
				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 = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
                                // 执行init方法
				namespaceHandler.init();
                                // 把新的namespaceUri与namespaceHandler映射关系组装到map中
				handlerMappings.put(namespaceUri, namespaceHandler);
				return namespaceHandler;
			}

上述解析namespaceURI对应的handler类,对应步骤又可分为如下几步,
step1:从spring.handler里面获取映射关系;
step2:根据namespaceURI从映射关系map中获取对应的处理类handler;
step3:通过反射获取handler对象,并执行init方法,完成自定义标签注册;

回到顶部

3. 总结

本文主要分析了xml标签的解析,主要步骤与流程图上述代码分析与时序图,通过调试可以清晰观察到解析过程,后续将通过示例分析beanDefinition类的相关属性。

相关推荐

js向对象中添加元素(对象,数组) js对象里面添加元素

一、添加一个元素对象名["属性名"]=值(值:可以是一个值,可以是一个对象,也可以是一个数组)这样添加进去的元素,就是一个值或对象或数组...

JS小技巧,如何去重对象数组?(一)

大家好,关于数组对象去重的业务场景,想必大家都遇到过类似的需求吧,这对这样的需求你是怎么做的呢。下面我就先和大家分享下如果是基于对象的1个属性是怎么去重实现的。方法一:使用.filter()和....

「C/C++」之数组、vector对象和array对象的比较

数组学习过C语言的,对数组应该都不会陌生,于是这里就不再对数组进行展开介绍。模板类vector模板类vector类似于string,也是一种动态数组。能够在运行阶段设置vector对象的长度,可以在末...

如何用sessionStorage保存对象和数组

背景:在工作中,我将[{},{}]对象数组形式,存储到sessionStorage,然后ta变成了我看不懂的形式,然后我想取之用之,发现不可能了~记录这次深刻的教训。$clickCouponIndex...

JavaScript Array 对象 javascript的array对象

Array对象Array对象用于在变量中存储多个值:varcars=["Saab","Volvo","BMW"];第一个数组元素的索引值为0,第二个索引值为1,以此类推。更多有...

JavaScript中的数组Array(对象) js array数组

1:数组Array:-数组也是一个对象-数组也是用来存储数据的-和object不同,数组中可以存储一组有序的数据,-数组中存储的数据我们称其为元素(element)-数组中的每一个元素都有一...

数组和对象方法&amp;数组去重 数组去重的5种方法前端

列举一下JavaScript数组和对象有哪些原生方法?数组:arr.concat(arr1,arr2,arrn);--合并两个或多个数组。此方法不会修改原有数组,而是返回一个新数组...

C++ 类如何定义对象数组?初始化数组?linux C++第43讲

对象数组学过C语言的读者对数组的概念应该很熟悉了。数组的元素可以是int类型的变量,例如int...

ElasticSearch第六篇:复合数据类型-数组,对象

在ElasticSearch中,使用JSON结构来存储数据,一个Key/Value对是JSON的一个字段,而Value可以是基础数据类型,也可以是数组,文档(也叫对象),或文档数组,因此,每个JSON...

第58条:区分数组对象和类数组对象

示例设想有两个不同类的API。第一个是位向量:有序的位集合varbits=newBitVector;bits.enable(4);bits.enable([1,3,8,17]);b...

八皇后问题解法(Common Lisp实现)

如何才能在一张国际象棋的棋盘上摆上八个皇后而不致使她们互相威胁呢?这个著名的问题可以方便地通过一种树搜索方法来解决。首先,我们需要写一个函数来判断棋盘上的两个皇后是否互相威协。在国际象棋中,皇后可以沿...

visual lisp修改颜色的模板函数 怎么更改visual studio的配色

(defunBF-yansemokuai(tuyuanyanse/ss)...

用中望CAD加载LISP程序技巧 中望cad2015怎么加载燕秀

1、首先请加载lisp程序,加载方法如下:在菜单栏选择工具——加载应用程序——添加,选择lisp程序然后加载,然后选择添加到启动组。2、然后是添加自定义栏以及图标,方法如下(以...

图的深度优先搜索和广度优先搜索(Common Lisp实现)

为了便于描述,本文中的图指的是下图所示的无向图。搜索指:搜索从S到F的一条路径。若存在,则以表的形式返回路径;若不存在,则返回nil。...

两个有助于理解Common Lisp宏的例子

在Lisp中,函数和数据具有相同的形式。这是Lisp语言的一个重大特色。一个Lisp函数可以分析另一个Lisp函数;甚至可以和另一个Lisp函数组成一个整体,并加以利用。Lisp的宏,是实现上述特色的...