IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring源码系列:标签的解析原理 -> 正文阅读

[Java知识库]Spring源码系列:标签的解析原理

前言

在了解了Spring容器加载资源的一个过程后,其对资源加载的最后一步则是生成对应的BeanDefinition,在本篇文章,我们着重讲Spring是如何对配置文件的标签进行解析的流程,parseDefaultElementparseCustomElement方法。

先从parseDefaultElement开始:

一. 默认标签的解析

默认标签的解析,会分别对4种不同的标签做处理:

  • importimportBeanDefinitionResource()
  • aliasprocessAliasRegistration()
  • beanprocessBeanDefinition()
  • beansdoRegisterBeanDefinitions()
public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {
	// 这里的ele对象,是Spring容器已经将配置资源读取、加载完成并转化为Document对象了,可以理解为文档对象的节点。
	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}
}

还记得我们上篇文章中讲的小案例吗?里面用的就是bean标签,这也是最基本最常用的一个标签,因此我们着重来讲它。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
	   xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="
   	.....省略">
   	<bean name="user" class="org.springframework.beans.User">
		<property name="id" value="1" />
		<property name="name" value="你好" />
	</bean>
</beans>

1.1 bean标签的解析和注册

让我们来深入processBeanDefinition方法:

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
	// 1.进行Bean元素解析,返回的实例bdHolder中,已经包含配置文件里面的各种属性了,例如:class、name、id、alias等。
	BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
	if (bdHolder != null) {
		// 2.若默认标签的子节点下还有自定义的属性,那么还需要再次解析。
		bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
		try {
			// 3.解析完成后,需要对解析后的bdHolder进行注册。
			BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
		} catch (BeanDefinitionStoreException ex) {
			// ...
		}
		// 4.发送响应事件,通知相关监听器,该bean已经加载完成了。
		getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
	}
}

1.1.1 解析BeanDefinition

这一小节,我们主要关注这行代码BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);

public class BeanDefinitionParserDelegate {
	@Nullable
	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
		// 解析id属性和name属性
		String id = ele.getAttribute(ID_ATTRIBUTE);
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
		// 1.分割name属性,根据 ,; 来分割,MULTI_VALUE_ATTRIBUTE_DELIMITERS = ",; ";
		List<String> aliases = new ArrayList<>();
		if (StringUtils.hasLength(nameAttr)) {
			String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			aliases.addAll(Arrays.asList(nameArr));
		}
	
		String beanName = id;
		if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
			beanName = aliases.remove(0);
			// log...
		}
	
		if (containingBean == null) {
			checkNameUniqueness(beanName, aliases, ele);
		}
		// 2.这里会进一步解析其他所有属性,并统一封装成beanDefinition,最后返回的实例对象为GenericBeanDefinition类型,
		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
		if (beanDefinition != null) {
			if (!StringUtils.hasText(beanName)) {
				try {
					// 3.若不存在beanName,则根据Spring的命名规则为当前bean生成对应的beanName
					if (containingBean != null) {
						beanName = BeanDefinitionReaderUtils.generateBeanName(
								beanDefinition, this.readerContext.getRegistry(), true);
					}
					else {
						beanName = this.readerContext.generateBeanName(beanDefinition);
						String beanClassName = beanDefinition.getBeanClassName();
						if (beanClassName != null &&
								beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
								!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
							aliases.add(beanClassName);
						}
					}
					// log...
				}
				catch (Exception ex) {
					error(ex.getMessage(), ele);
					return null;
				}
			}
			String[] aliasesArray = StringUtils.toStringArray(aliases);
			// 4.将获取到的信息封装成BeanDefinitionHolder的实例中并返回。
			return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
		}
		return null;
	}
}

简而言之就是:

  1. 解析idname属性,并对name属性进行分割。
  2. 解析其他属性。beanName没有,则自动生成个。
  3. 将上述获取到结果的再进行整合,得到最终的BeanDefinitionHolder实例对象。

那我们再来细看下第二步的 “解析其他属性” 的过程:
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);

形式上就像根据不同的Key,来获取不同的Value而已value = getAttribute(key)

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

	this.parseState.push(new BeanEntry(beanName));
	// 解析class属性
	String className = null;
	if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
		className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
	}
	// 解析parent属性
	String parent = null;
	if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
		parent = ele.getAttribute(PARENT_ATTRIBUTE);
	}

	try {
		// 创建一个用于承载属性的GenericBeanDefinition对象
		AbstractBeanDefinition bd = createBeanDefinition(className, parent);
		// 硬编码解析默认bean的各种属性
		parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
		// 提取description,bean的一个描述信息
		bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
		// 解析元数据
		parseMetaElements(ele, bd);
		// 解析lookup-method属性:把一个方法声明为返回某种类型的bean的标签。用于设计插拔功能
		parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
		// 解析replaced-method属性:可以在运行时用新的方法替换旧的方法
		parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
		// 解析构造函数参数
		parseConstructorArgElements(ele, bd);
		// 解析property子元素
		parsePropertyElements(ele, bd);
		// 解析qualifier子元素
		parseQualifierElements(ele, bd);

		bd.setResource(this.readerContext.getResource());
		bd.setSource(extractSource(ele));

		return bd;
	}
	catch {
		// 各种catch...
	}
	finally {
		this.parseState.pop();
	}

	return null;
}

这里的解析其他属性,也就是解析classdescriptionproperty等属性。我们先来看下,用于承载这些属性的GenericBeanDefinition对象:

(1) GenericBeanDefinition对象

BeanDefinition是一个接口,他是配置文件<bean>元素标签在容器内部的一个表现形式。 例如:<bean>标签拥有classscopelazy-init等配置属性。而BeanDefinition则提供了对应的beanClassscopelazyInit操作方法。其为一一对应的一个关系。

Spring中,BeanDefinition的实现有这3个常见的,它们都继承了AbstractBeanDefinition

  • RootBeanDefinition:最常用的实现类,对应一般性的<bean>元素标签。
  • ChildBeanDefinition:用于标识子<bean>标签。
  • GenericBeanDefinition:一种一站式服务类

Spring容器一般在读取加载完配置资源文件后,将其转为BeanDefinition类型实例的内部表示,而这些实例一般会注册到BeanDefinitionRegistry中。其主要以map形式来保存,后续操作都是从BeanDefinitionRegistry中读取配置信息的。

(2) 解析各种属性

我们来看下parseBeanDefinitionAttributes()这个方法:

建议:如果想知道里面解析了哪些具体的属性,读者可以展开代码瞅瞅,否则,这段代码其实没什么好看的。

public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
		@Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {
	// 1.解析scope属性。singleton这个标签已经过时了,改用scope来声明
	if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
		error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
	}else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
		bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
	}else if (containingBean != null) {
		bd.setScope(containingBean.getScope());
	}
	// 2.解析abstract属性
	if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
		bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
	}
	// 3.解析lazy-init属性
	String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
	if (isDefaultValue(lazyInit)) {
		lazyInit = this.defaults.getLazyInit();
	}
	bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
	// 4.解析autowire属性
	String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
	bd.setAutowireMode(getAutowireMode(autowire));
	// 5.解析depends-on属性
	if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
		String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
		bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
	}
	// 6.解析autowire-candidate属性
	String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
	if (isDefaultValue(autowireCandidate)) {
		String candidatePattern = this.defaults.getAutowireCandidates();
		if (candidatePattern != null) {
			String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
			bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
		}
	}else {
		bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
	}
	// 7.解析primary属性
	if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
		bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
	}
	// 8.解析init-method属性
	if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
		String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
		bd.setInitMethodName(initMethodName);
	}else if (this.defaults.getInitMethod() != null) {
		bd.setInitMethodName(this.defaults.getInitMethod());
		bd.setEnforceInitMethod(false);
	}
	// 9.解析destroy-method属性
	if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
		String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
		bd.setDestroyMethodName(destroyMethodName);
	}else if (this.defaults.getDestroyMethod() != null) {
		bd.setDestroyMethodName(this.defaults.getDestroyMethod());
		bd.setEnforceDestroyMethod(false);
	}
	// 10.解析factory-method属性
	if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
		bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
	}
	// 11.解析factory-bean 属性
	if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
		bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
	}

	return bd;
}

(3) lookup-method标签的作用

案例如下:

项目结构
在这里插入图片描述
User类:

public class User {
	void getName(){
		System.out.println("I am User");
	}
}

Student类:

public class Student extends User{
	void getName(){
		System.out.println("I am Student");
	}
}

GetNameTest类:

public abstract class GetNameTest {
	public abstract User getBean();

	public void getName() {
		// 调用了抽象方法getBean(),返回User实例
		this.getBean().getName();
	}
}

test.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
	   xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="
   	http://www.springframework.org/schema/beans
   	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   	http://www.springframework.org/schema/aop
   	http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
   	http://www.springframework.org/schema/tx
   	http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   	http://www.springframework.org/schema/context
   	http://www.springframework.org/schema/context/spring-context-3.0.xsd">
   	
	<bean id="getNameTest" class="org.springframework.beans.GetNameTest">
		<lookup-method name="getBean" bean="student"/>
	</bean>
	<bean id="student" class="org.springframework.beans.Student"/>
</beans>

Test类:

public class Test {
	public static void main(String[] args) {
		ClassPathResource resource = new ClassPathResource("test.xml");
		DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
		reader.loadBeanDefinitions(resource);

		GetNameTest getNameTest = (GetNameTest) factory.getBean("getNameTest");
		getNameTest.getName();
	}
}

结果如下:
在这里插入图片描述
原理分析:

parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
代码展开如下:
public void parseLookupOverrideSubElements(Element beanEle, MethodOverrides overrides) {
	NodeList nl = beanEle.getChildNodes();
	for (int i = 0; i < nl.getLength(); i++) {
		Node node = nl.item(i);
		// 仅仅在 Spring默认bean的子元素为lookup-method 时有效
		if (isCandidateElement(node) && nodeNameEquals(node, LOOKUP_METHOD_ELEMENT)) {
			Element ele = (Element) node;
			// 获取要修饰的方法
			String methodName = ele.getAttribute(NAME_ATTRIBUTE);
			// 获取配置返回的bean引用
			String beanRef = ele.getAttribute(BEAN_ELEMENT);
			// LookupOverride实体对象用于记录属性 ,最终记录在AbstractBeanDefinition类中的methodOverrides中
			LookupOverride override = new LookupOverride(methodName, beanRef);
			override.setSource(extractSource(ele));
			overrides.addOverride(override);
		}
	}
}

也就是Spring会读取特定的<lookup-method>标签,并根据配置的bean的具体的引用来执行对应的方法。代码中,getBean()并没有具体的实现,但是我们通过XML配置,将该抽象方法声明为返回了某种类型的bean,在本案例中就是返回了Student对象实例。

再把两者代码结合在一起来看看:
在这里插入图片描述
所以,当我们在业务中,不再需要Student类的相关逻辑了,需要Teacher类的getName()方法,我们只需要换一个引用即可,该标签常用于插拔功能。

(4) replaced-method标签的作用

案例如下:
GetNameTest类:

public class GetNameTest {
	public void getName() {
		System.out.println("Hello");
	}
}

Change类:需要实现MethodReplacer接口

import org.springframework.beans.factory.support.MethodReplacer;

import java.lang.reflect.Method;

public class Change implements MethodReplacer {
	@Override
	public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
		System.out.println("Method Change!");
		return null;
	}
}

test.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
	   xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="
   	http://www.springframework.org/schema/beans
   	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   	http://www.springframework.org/schema/aop
   	http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
   	http://www.springframework.org/schema/tx
   	http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   	http://www.springframework.org/schema/context
   	http://www.springframework.org/schema/context/spring-context-3.0.xsd">

	<bean id="getNameTest" class="org.springframework.beans.GetNameTest">
		<replaced-method name="getName" replacer="change"/>
	</bean>
	<bean id="change" class="org.springframework.beans.Change"/>
</beans>

Test类:

public class Test {
	public static void main(String[] args) {
		ClassPathResource resource = new ClassPathResource("test.xml");
		DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
		reader.loadBeanDefinitions(resource);

		GetNameTest getNameTest = (GetNameTest) factory.getBean("getNameTest");
		getNameTest.getName();
	}
}

结果如下:
在这里插入图片描述
可以看出来,<replaced-method>标签允许我们在运行时用新方法代替现有的方法。

那么上述两种标签有什么区别呢?

  • <replaced-method>:一般用于替代现有逻辑。
  • <lookup-method>:一般用于执行其他方案,需要创建其他Bean

源码分析:(建议结合XML配置来看)

public void parseReplacedMethodSubElements(Element beanEle, MethodOverrides overrides) {
	NodeList nl = beanEle.getChildNodes();
	for (int i = 0; i < nl.getLength(); i++) {
		Node node = nl.item(i);
		// 仅当在默认bean的子标签为replace-method情况下有效
		if (isCandidateElement(node) && nodeNameEquals(node, REPLACED_METHOD_ELEMENT)) {
			Element replacedMethodEle = (Element) node;
			// 提取要替换的旧方法
			String name = replacedMethodEle.getAttribute(NAME_ATTRIBUTE);
			// 提取对应新的替换方法
			String callback = replacedMethodEle.getAttribute(REPLACER_ATTRIBUTE);
			ReplaceOverride replaceOverride = new ReplaceOverride(name, callback);
			// Look for arg-type match elements.
			List<Element> argTypeEles = DomUtils.getChildElementsByTagName(replacedMethodEle, ARG_TYPE_ELEMENT);
			for (Element argTypeEle : argTypeEles) {
				// 记录其参数
				String match = argTypeEle.getAttribute(ARG_TYPE_MATCH_ATTRIBUTE);
				match = (StringUtils.hasText(match) ? match : DomUtils.getTextValue(argTypeEle));
				if (StringUtils.hasText(match)) {
					replaceOverride.addTypeIdentifier(match);
				}
			}
			replaceOverride.setSource(extractSource(replacedMethodEle));
			overrides.addOverride(replaceOverride);
		}
	}
}

注意:

  • 两种标签,最后都是创建了MethodOverride实例:LookupOverrideReplaceOverride。用于记录相关参数。而其最终都是记录在AbstractBeanDefinition类中的methodOverrides属性中。

(5) constructor-arg标签的作用

首先,我们先来看下这个标签有什么作用。案例如下:
User类:

public class User {
	private int id;
	private String name;
	private Map<String, String> map;

	public User(int id, String name, Map<String, String> map) {
		this.id = id;
		this.name = name;
		this.map = map;
	}
	
	public Map<String, String> getMap() {
		return map;
	}

	public void setMap(Map<String, String> map) {
		this.map = map;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

Test方法:

@org.junit.jupiter.api.Test
public void test() {
	BeanFactory factory = new DefaultListableBeanFactory();
	XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) factory);

	ClassPathResource resource = new ClassPathResource("user.xml");
	reader.loadBeanDefinitions(resource);

	User user = (User) factory.getBean("user");
	System.out.println(user.getId());
	System.out.println(user.getName());
	System.out.println(user.getMap().get("key"));
}

user.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
	   xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="
   	http://www.springframework.org/schema/beans
   	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   	http://www.springframework.org/schema/aop
   	http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
   	http://www.springframework.org/schema/tx
   	http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   	http://www.springframework.org/schema/context
   	http://www.springframework.org/schema/context/spring-context-3.0.xsd">
	<bean name="user" class="org.springframework.beans.User">
		<constructor-arg index="0">
			<value>1</value>
		</constructor-arg>
		<constructor-arg index="1">
			<value>名字</value>
		</constructor-arg>
		<constructor-arg index="2">
			<map>
				<entry key="key" value="value"/>
			</map>
		</constructor-arg>
	</bean>
</beans>

结果如下:
在这里插入图片描述
可以看到,该标签在功能上,实现了初始化Bean的时候,将设置的参数传了进去。那么我们再来看下Spring默认解析标签时对该标签解析的调用起点位置:

parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
↓↓↓↓↓↓
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
	NodeList nl = beanEle.getChildNodes();
	// 遍历所有子元素,即提取所有构造函数的参数并解析
	for (int i = 0; i < nl.getLength(); i++) {
		Node node = nl.item(i);
		if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
			// 具体的解析函数
			parseConstructorArgElement((Element) node, bd);
		}
	}
}
↓↓↓↓↓↓parseConstructorArgElement↓↓↓↓↓↓
public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
	// 提取index属性,对应构造函数的第几个参数
	String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
	// 提取type属性
	String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
	// 提取name属性
	String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
	if (StringUtils.hasLength(indexAttr)) {
		try {
			// 构造函数的参数下标,不可能比0小
			int index = Integer.parseInt(indexAttr);
			if (index < 0) {
				error("'index' cannot be lower than 0", ele);
			}
			else {
				try {
					this.parseState.push(new ConstructorArgumentEntry(index));
					// 解析ele对应的属性元素
					Object value = parsePropertyValue(ele, bd, null);
					ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
					if (StringUtils.hasLength(typeAttr)) {
						valueHolder.setType(typeAttr);
					}
					if (StringUtils.hasLength(nameAttr)) {
						valueHolder.setName(nameAttr);
					}
					valueHolder.setSource(extractSource(ele));
					//不允许重复指定相同的参数
					if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
						error("Ambiguous constructor-arg entries for index " + index, ele);
					}
					else {
						// 将type name index属性都封装进去,并将最终的封装实例塞到BeanDefinition的ConstructorArgumentValues下的indexedArgumentValues属性中
						bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
					}
				}
				finally {
					this.parseState.pop();
				}
			}
		}
		catch (NumberFormatException ex) {
			error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
		}
	}
	else {
		// 若没有指定index属性,则自动寻找
		try {
			this.parseState.push(new ConstructorArgumentEntry());
			Object value = parsePropertyValue(ele, bd, null);
			// valueHolder用于封装解析出来的元素
			ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
			// 将type name index属性都封装进去,并将最终的封装实例,塞到BeanDefinition的ConstructorArgumentValues下的genericArgumentValues属性中
			if (StringUtils.hasLength(typeAttr)) {
				valueHolder.setType(typeAttr);
			}
			if (StringUtils.hasLength(nameAttr)) {
				valueHolder.setName(nameAttr);
			}
			valueHolder.setSource(extractSource(ele));
			bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
		}
		finally {
			this.parseState.pop();
		}
	}
}

上述流程当中,分为两种情况:

  • 情况1:配置中指定了index属性:
  • 情况2:配置中没有指定index属性。

两种情况的前几步骤是一样的:

  1. 解析Constructor-arg的子元素。
  2. 使用valueHolder实例去封装解析出来的元素。

唯一不同的就是:

  • 情况1:将valueHolder实例封装于BeanDefinition下的indexedArgumentValues属性中。
  • 情况2:将valueHolder实例封装于BeanDefinition下的genericArgumentValues属性中。

在此之前,先来讲一下解析构造子元素的方法原理parsePropertyValue:

Object value = parsePropertyValue(ele, bd, null);
↓↓↓↓↓↓
@Nullable
public Object parsePropertyValue(Element ele, BeanDefinition bd, @Nullable String propertyName) {
	String elementName = (propertyName != null ?
			"<property> element for property '" + propertyName + "'" :
			"<constructor-arg> element");

	// 一个属性只能对应一种类型:ref, value, list, etc的一种
	NodeList nl = ele.getChildNodes();
	Element subElement = null;
	for (int i = 0; i < nl.getLength(); i++) {
		Node node = nl.item(i);
		//对应description或者meta则不处理
		if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
				!nodeNameEquals(node, META_ELEMENT)) {
			// Child element is what we're looking for.
			if (subElement != null) {
				error(elementName + " must not contain more than one sub-element", ele);
			}
			else {
				subElement = (Element) node;
			}
		}
	}
	// 解析constructor-arg上的ref属性
	boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
	// 解析constructor-arg上的value属性
	boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
	/**
	 * constructor-arg标签的使用不存在以下两种情形:
	 * 		1.同时存在ref和value属性
	 * 		2.存在ref/value属性,同时又有子元素。
	 */
	if ((hasRefAttribute && hasValueAttribute) ||
			((hasRefAttribute || hasValueAttribute) && subElement != null)) {
		error(elementName +
				" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
	}
	// ref属性的处理,最终用RuntimeBeanReference去封装对应的ref名称
	if (hasRefAttribute) {
		String refName = ele.getAttribute(REF_ATTRIBUTE);
		if (!StringUtils.hasText(refName)) {
			error(elementName + " contains empty 'ref' attribute", ele);
		}
		RuntimeBeanReference ref = new RuntimeBeanReference(refName);
		ref.setSource(extractSource(ele));
		return ref;
	}
	// value属性的处理,使用TypedStringValue封装
	else if (hasValueAttribute) {
		TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
		valueHolder.setSource(extractSource(ele));
		return valueHolder;
	}
	// 解析子元素
	else if (subElement != null) {
		return parsePropertySubElement(subElement, bd);
	}
	else {
		// 若以上情况都不满足,说明配置有问题
		error(elementName + " must specify a ref or value", ele);
		return null;
	}
}

再来看下Spring对Constructor-arg的子元素是如何解析的:

parsePropertySubElement(subElement, bd);
↓↓↓↓↓↓
@Nullable
public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd) {
	return parsePropertySubElement(ele, bd, null);
}
↓↓↓↓↓↓
@Nullable
public Object parsePropertySubElement(Element ele, @Nullable BeanDefinition bd, @Nullable String defaultValueType) {
	if (!isDefaultNamespace(ele)) {
		return parseNestedCustomElement(ele, bd);
	}
	else if (nodeNameEquals(ele, BEAN_ELEMENT)) {
		// ...
	}
	else if (nodeNameEquals(ele, REF_ELEMENT)) {
		// ...
	}
	else if (nodeNameEquals(ele, IDREF_ELEMENT)) {
		return parseIdRefElement(ele);
	}
	// 解析 value 标签
	else if (nodeNameEquals(ele, VALUE_ELEMENT)) {
		return parseValueElement(ele, defaultValueType);
	}
	// 解析 null 标签
	else if (nodeNameEquals(ele, NULL_ELEMENT)) {
		TypedStringValue nullHolder = new TypedStringValue(null);
		nullHolder.setSource(extractSource(ele));
		return nullHolder;
	}
	// 解析 array 标签
	else if (nodeNameEquals(ele, ARRAY_ELEMENT)) {
		return parseArrayElement(ele, bd);
	}
	// 解析 list 标签
	else if (nodeNameEquals(ele, LIST_ELEMENT)) {
		return parseListElement(ele, bd);
	}
	// 解析 set 标签
	else if (nodeNameEquals(ele, SET_ELEMENT)) {
		return parseSetElement(ele, bd);
	}
	// 解析 map 标签
	else if (nodeNameEquals(ele, MAP_ELEMENT)) {
		return parseMapElement(ele, bd);
	}
	// 解析 props 标签
	else if (nodeNameEquals(ele, PROPS_ELEMENT)) {
		return parsePropsElement(ele);
	}
	else {
		error("Unknown property sub-element: [" + ele.getNodeName() + "]", ele);
		return null;
	}
}

到这里,我们可以了解Spring会对可支持的子类进行分类处理,而具体的处理就不再一一展开细说了。

(6) 解析property子元素

老样子,先来看下它有什么作用:

@org.junit.jupiter.api.Test
public void test() {
	BeanFactory factory = new DefaultListableBeanFactory();
	XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) factory);

	ClassPathResource resource = new ClassPathResource("user.xml");
	reader.loadBeanDefinitions(resource);

	User user = (User) factory.getBean("user");
	System.out.println(user.getId());
}

user.xml

<bean name="user" class="org.springframework.beans.User">
	<property name="id" value="1"/>
</bean>

结果如下:
在这里插入图片描述
看来,其也就是用于给类的字段赋值的,我们来看下其源码。

parsePropertyElements(ele, bd);
↓↓↓↓↓↓
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
	NodeList nl = beanEle.getChildNodes();
	for (int i = 0; i < nl.getLength(); i++) {
		Node node = nl.item(i);
		if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
			parsePropertyElement((Element) node, bd);
		}
	}
}
↓↓↓↓↓↓
public void parsePropertyElement(Element ele, BeanDefinition bd) {
	String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
	if (!StringUtils.hasLength(propertyName)) {
		error("Tag 'property' must have a 'name' attribute", ele);
		return;
	}
	this.parseState.push(new PropertyEntry(propertyName));
	try {
		if (bd.getPropertyValues().contains(propertyName)) {
			error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
			return;
		}
		// 关键代码
		Object val = parsePropertyValue(ele, bd, propertyName);
		PropertyValue pv = new PropertyValue(propertyName, val);
		parseMetaElements(ele, pv);
		pv.setSource(extractSource(ele));
		bd.getPropertyValues().addPropertyValue(pv);
	}
	finally {
		this.parseState.pop();
	}
}

Object val = parsePropertyValue(ele, bd, propertyName);这行代码,我们在第5小节,解析constructor-arg标签的时候就已经讲过了,无非就是解析refvalue以及子元素(setmap等标签)的过程,这里也就不做展开。

最终解析好的属性则会记录在BeanDefinition中的propertyValues属性中。

(7) qualifier子元素

Spring框架中进行自动装配时,Spring容器中匹配的候选Bean的数量有且只有一个。 Spring允许我们通过Qualifier指定注入Bean的名称,这样可以用于消除歧义。 若找不到一个匹配的bean时,Spring容器将抛出BeanCreationnException异常。

贴出注解形式的伪代码:

public interface User {
    void log();
}

@Service("teacher")
public class Teacher implements User {
    @Override
    public void log() {
        System.out.println("i am teacher");
    }
}

@Service("student")
public class Student implements User {
    @Override
    public void log() {
        System.out.println("i am student");
    }
}

public class Test {
    @Autowired
    @Qualifier("student")
    private User user;

    @RequestMapping("/test")
    public void test() {
        // 输出:i am student
        user.log();
    }
}

1.1.2 AbstractBeanDefinition的属性

1.1.1节中,对标签的解析和属性的承载,也就是完成了XML文档到GenericBeanDefinition的转换。其中XML的所有配置都可以在GenericBeanDefinition的实例类中找到对应的位置。大部分的通用属性则保存在AbstractBeanDefinition中。

我们来看下这个类中的属性:

public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor
		implements BeanDefinition, Cloneable {
	private volatile Object beanClass;
	// bean 的作用范围,对应bean的scope属性
	private String scope = SCOPE_DEFAULT;
	// 是否抽象,对应bean的abstract
	private boolean abstractFlag = false;
	// 是否延迟加载。对应bean的lazy-init
	private Boolean lazyInit;
	// 自动注入模式,对应bean的autowire
	private int autowireMode = AUTOWIRE_NO;
	private int dependencyCheck = DEPENDENCY_CHECK_NONE;
	// 用来表示一个bean的实例化依靠另一个bean先实例化,对应bean标签下的depend-on
	private String[] dependsOn;
	// 设置为false的话,表示容器在查找自动装配的对象的时候,将不考虑该bean,对应autowire-candidate
	private boolean autowireCandidate = true;
	// 当自动装配出现多个bean的候选者的时候,作为首选者
	private boolean primary = false;
	// 用于记录Qualifier,对影子元素qualifier
	private final Map<String, AutowireCandidateQualifier> qualifiers = new LinkedHashMap<>();

	private Supplier<?> instanceSupplier;
// 允许访问非公开的构造器和方法,由程序设计
	private boolean nonPublicAccessAllowed = true;
	/**
	 * 是否以一种宽送的模式解析构造函数,默认为true
	 * 若为false,那么以下情况抛出异常,因为Spring无法准确定位到哪一个构造函数。
	 * interface User{}
	 * class UserImpl implements User{};
	 * class Test{
	 *     Test(User u){}
	 *     Test(UserImpl u){}
	 * }
	 */
	private boolean lenientConstructorResolution = true;
	// 对应bean属性的factory-bean
	private String factoryBeanName;

	private String factoryMethodName;
	// 记录构造函数注入的属性,对于bean属性的constructor-arg
	private ConstructorArgumentValues constructorArgumentValues;
	// 普通属性集合
	private MutablePropertyValues propertyValues;
	// 方法重写的持有者,记录lookup-method和replaced-method元素
	private MethodOverrides methodOverrides = new MethodOverrides();
	// 初始化方法,对应init-method
	private String initMethodName;
	// 销毁方法,对应destroy-method
	private String destroyMethodName;
	// 是否执行初始化方法,程序设置
	private boolean enforceInitMethod = true;
	// 是否执行销毁方法,程序设置
	private boolean enforceDestroyMethod = true;
	// 是否是用户定义的,创建AOP的时候为true,程序设置
	private boolean synthetic = false;
	/**
	 * 定义这个bean的应用
	 * APPLICATION:用户
	 * INFRASTRUCTURE:完全内部使用,与用户无关
	 * SUPPORT:某些复杂配置的一部分
	 * 程序设置
	 */
	private int role = BeanDefinition.ROLE_APPLICATION;
	// bean的描述信息
	private String description;
	// bean定义的资源
	private Resource resource;
}

1.1.3 解析默认标签中的自定义标签

再从头看起:默认标签的解析processBeanDefinition方法:

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
	// 1.进行Bean元素解析,返回的实例bdHolder中,已经包含配置文件里面的各种属性了,例如:class、name、id、alias等。
	BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
	if (bdHolder != null) {
		// 2.若默认标签的子节点下还有自定义的属性,那么还需要再次解析。
		bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
		try {
			// 3.解析完成后,需要对解析后的bdHolder进行注册。
			BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
		} catch (BeanDefinitionStoreException ex) {
			// ...
		}
		// 4.发送响应事件,通知相关监听器,该bean已经加载完成了。
		getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
	}
}

上文已经把第一步中,对配置文件的一个基础解析和提取已经讲完了。至此我们继续第二步,解析默认标签下的自定义属性bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
什么叫默认标签下的自定义标签呢?上代码:

<bean id="teacher" class="org.springframework.beans.Teacher">
	<mybean:user username="aaa"/>
</bean>

注意,这里的自定义标签对应的是bean 的一个属性。

bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
↓↓↓↓↓↓
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(Element ele, BeanDefinitionHolder originalDef) {
	// 都三个参数的作用:父类bean,当对某个嵌套配置进行分析时,需要用到父类的scope属性
	// 而当前分析默认标签的自定义标签这个场景下,分析的是顶层配置,因此传递null
	return decorateBeanDefinitionIfRequired(ele, originalDef, null);
}
↓↓↓↓↓↓
public BeanDefinitionHolder decorateBeanDefinitionIfRequired(
			Element ele, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {

	BeanDefinitionHolder finalDefinition = originalDef;

	// 遍历所有的属性,看看是否有适用于修饰的属性
	NamedNodeMap attributes = ele.getAttributes();
	for (int i = 0; i < attributes.getLength(); i++) {
		Node node = attributes.item(i);
		finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
	}

	// 遍历所有子节点,看看是否有适用于修饰的子元素【’;
	NodeList children = ele.getChildNodes();
	for (int i = 0; i < children.getLength(); i++) {
		Node node = children.item(i);
		if (node.getNodeType() == Node.ELEMENT_NODE) {
			finalDefinition = decorateIfRequired(node, finalDefinition, containingBd);
		}
	}
	return finalDefinition
}

从代码中我们可以看出来,无论是哪一个代码块,最终都执行了decorateIfRequired方法:

public BeanDefinitionHolder decorateIfRequired(
			Node node, BeanDefinitionHolder originalDef, @Nullable BeanDefinition containingBd) {
	// 1.获取自定义标签的命名空间
	String namespaceUri = getNamespaceURI(node);
	if (namespaceUri != null && !isDefaultNamespace(namespaceUri)) {
		// 2.根据命名空间找到对应的处理器
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler != null) {
			// 3.进行修饰
			BeanDefinitionHolder decorated =
					handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd));
			if (decorated != null) {
				return decorated;
			}
		}
		// ...
	}
	return originalDef;
}
  1. 获取属性或元素的命名空间。
  2. 判断该元素是否适用于自定义标签解析的条件。
  3. 找到自定义类型对应的handler进行进一步解析(第二章节具体展开)。

1.1.3 注册解析的BeanDefinition

对于配置文件,Spring容器负责加载解析,将其转化为Document实例。而上述流程则是将配置解析完了。相关的装饰功能也做好了,那么此时BeanDefinition已经满足使用的要求了,剩下的工作即是注册了。

代码入口如下:

BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());

我们来深入了解:

public abstract class BeanDefinitionReaderUtils {
	public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException {

		// 1.使用beanName作为唯一的标识key注册
		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// 2.注册所有的别名
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}
}

可见对于BeanDefinition的注册分成了两个部分:

  1. 通过beanName的注册。
  2. 通过别名的注册。

(1) 通过beanName注册

整体而言,我们都将直到Spring容器中存储BeanDefinition的方式就是放在map中,beanName则作为mapkey。具体的我们来看下:

registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
↓↓↓↓↓↓↓
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
		implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
	@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {
		// 1.beanName和对应的BeanDefinition肯定不能为空或者null,否则我还注册啥呢?注册也就是将其变成key-value而已
		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
				// 2.进行注册前的最后一次校验
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}

		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
		// 若该beanName已经被注册
		if (existingDefinition != null) {
			// 3.若对于的beanName已经注册并且在配置中配置了bean不可以被覆盖,那么此时抛出异常
			if (!isAllowBeanDefinitionOverriding()) {
				throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
			}
			// ...各种log记录
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			if (hasBeanCreationStarted()) {
				// beanDefinitionMap作为全局变量,可能存在并发访问的情况
				synchronized (this.beanDefinitionMap) {
					// 4.加入map缓存,该BeanDefinition通过注册
					this.beanDefinitionMap.put(beanName, beanDefinition);
					// 5.更新beanName列表
					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					removeManualSingletonName(beanName);
				}
			}
			else {
				// 此时还在启动注册的阶段,记录以下beanName
				this.beanDefinitionMap.put(beanName, beanDefinition);
				this.beanDefinitionNames.add(beanName);
				// 重置所有beanName对应的缓存
				removeManualSingletonName(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}
		// 单例bean只能注册一次
		if (existingDefinition != null || containsSingleton(beanName)) {
			resetBeanDefinition(beanName);
		}
		else if (isConfigurationFrozen()) {
			clearByTypeCache();
		}
	}
}

注册前的最后一次校验:((AbstractBeanDefinition) beanDefinition).validate();

public void validate() throws BeanDefinitionValidationException {
	// 主要是对AbstractBeanDefinition中的methodOverrides进行校验
	// 校验methodOverrides是否和工厂方法并存,或者methodOverrides对应的方法不存在。
	if (hasMethodOverrides() && getFactoryMethodName() != null) {
		throw new BeanDefinitionValidationException(
				"Cannot combine factory method with container-generated method overrides: " +
				"the factory method must create the concrete bean instance.");
	}
	if (hasBeanClass()) {
		prepareMethodOverrides();
	}
}

总结下就是:

  1. 校验methodOverrides属性。
  2. 处理beanName被注册但是不允许被覆盖的情况。
  3. 加入map进行注册。
  4. 清除缓存。

(2) 通过别名注册

registry.registerAlias(beanName, alias);
↓↓↓↓↓↓↓
public class SimpleAliasRegistry implements AliasRegistry {
	@Override
	public void registerAlias(String name, String alias) {
		Assert.hasText(name, "'name' must not be empty");
		Assert.hasText(alias, "'alias' must not be empty");
		synchronized (this.aliasMap) {
			// 若beanName和别名alias相同,则不记录alias,并删除它,
			if (alias.equals(name)) {
				this.aliasMap.remove(alias);
				if (logger.isDebugEnabled()) {
					logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
				}
			}
			else {
				String registeredName = this.aliasMap.get(alias);
				if (registeredName != null) {
					// 若已经存在了,则不需要重复注册
					if (registeredName.equals(name)) {
						return;
					}
					// 同样考虑到alias是否允许被覆盖
					if (!allowAliasOverriding()) {
						throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
								name + "': It is already registered for name '" + registeredName + "'.");
					}
					if (logger.isDebugEnabled()) {
						logger.debug("Overriding alias '" + alias + "' definition for registered name '" +
								registeredName + "' with new target name '" + name + "'");
					}
				}
				// 检查是否存在循环引用,例如”A->B存在时,若出现A->C->B则抛出异常
				checkForAliasCircle(name, alias);
				this.aliasMap.put(alias, name);
				if (logger.isTraceEnabled()) {
					logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
				}
			}
		}
	}
}

到这里,bean标签的解析和注册已经结束,但这个仅仅是针对默认的标签解析,而其他的alliasimportbeans标签,本文不再展开描述。

二. 自定义标签的解析

讲完parseDefaultElement方法后,也就是默认标签的解析流程。我们接下来去了解下自定义标签的使用和解析原理parseCustomElement方法。

2.1 自定义标签的运用

扩展一个自定义标签的配置大概需要这么几个步骤:

  1. 创建一个需要扩展的组件。
  2. 定义一个XSD文件描述组件的内容。
  3. 创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义。
  4. 创建一个Handler文件,扩展自NamespaceHandlerSupport,用于将组件注册到Spring容器中。
  5. 编写Spring.handlersSpring.schemas文件。

案例,项目结构如下:
在这里插入图片描述
1.创建一个需要扩展的组件User

package com.mytest;

public class User {
	private String email;
	private String name;

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

2.定义一个XSD文件描述组件的内容,user.xsd,这里定义了3种标签属性,id、name、email

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
		targetNamespace="http://www.ljjTest.com/schema/user"
		xmlns:tns="http://www.ljjTest.com/schema/user"
		elementFormDefault="qualified">
	<element name="user">
		<complexType>
			<attribute name="id" type="string"/>
			<attribute name="name" type="string"/>
			<attribute name="email" type="string"/>
		</complexType>
	</element>
</schema>

3.创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义,UserBeanDefinitionParser

package com.mytest;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;

public class UserBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser {
	protected Class getBeanClass(Element element) {
		return User.class;
	}

	protected void doParse(Element element, BeanDefinitionBuilder bean) {
		String name = element.getAttribute("name");
		String email = element.getAttribute("email");
		if (StringUtils.hasText(name)) {
			bean.addPropertyValue("name", name);
		}
		if (StringUtils.hasText(email)) {
			bean.addPropertyValue("email", email);
		}
	}
}

4.创建一个Handler文件,扩展自NamespaceHandlerSupport,用于将组件注册到Spring容器中,UserNamespaceHandler

package com.mytest;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class UserNamespaceHandler extends NamespaceHandlerSupport {
	@Override
	public void init() {
		registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
	}
}

5.编写Spring.handlersSpring.schemas文件。
Spring.handlers

http\://www.ljjTest.com/schema/user=com.mytest.UserNamespaceHandler

Spring.schemas

http\://www.ljjTest.com/schema/user.xsd=META-INF/user.xsd

6.编写XML配置Beanuser.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:myname="http://www.ljjTest.com/schema/user"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.ljjTest.com/schema/user http://www.ljjTest.com/schema/user.xsd">

	<myname:user id = "user" name="粽"  email = "胖粽"/>
</beans>

7.测试类Test

package com.mytest;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;

public class Test {
	@org.junit.jupiter.api.Test
	public void test() {
		BeanFactory factory = new DefaultListableBeanFactory();
		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) factory);

		ClassPathResource resource = new ClassPathResource("user.xml");
		reader.loadBeanDefinitions(resource);

		User user = (User) factory.getBean("user");
		System.out.println(user.getName());
		System.out.println(user.getEmail());
	}
}

结果如下:
在这里插入图片描述
Spring加载自定义标签的流程大概是这样:

  1. 遇到自定义标签后,就去Spring.handlersSpring.schemas文件中去找到对应的解析handlerXSD。默认位置是/META-INF/下。
  2. 找对于的handler后,自然而然能够找到解析元素的Parser。进而完成整个自定义元素的解析。

在这里插入图片描述

2.2 自定义标签解析原理

在了解自定义标签的使用和大致流程后,接下来让我们来看下Spring是如何对自定义标签进行解析的。让我们继续从parseCustomElement这个方法作为入口来解析:

public class BeanDefinitionParserDelegate {
	public BeanDefinition parseCustomElement(Element ele) {
		return parseCustomElement(ele, null);
	}
}
↓↓↓↓↓↓
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
	// 1.获取对应的命名空间(用的是第三方org.w3c.dom.Node的方法,不做深入了解)
	String namespaceUri = getNamespaceURI(ele);
	if (namespaceUri == null) {
		return null;
	}
	// 2.根据命名空间找到对应的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;
	}
	// 3.调用自定义的Handler进行解析
	return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

我们来看下第二步,提取自定义标签处理器。

2.2.1 提取自定义标签处理器

public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
	public NamespaceHandler resolve(String namespaceUri) {
		// 1.获取所有已经配置的handler映射
		Map<String, Object> handlerMappings = getHandlerMappings();
		// 2.根据命名空间找到对应的handler
		Object handlerOrClassName = handlerMappings.get(namespaceUri);
		if (handlerOrClassName == null) {
			return null;
		}
		else if (handlerOrClassName instanceof NamespaceHandler) {
			// 3.若已经做过了解析,那么直接从缓存读取
			return (NamespaceHandler) handlerOrClassName;
		}
		else {
			// 4.若没做过解析,拿到的是类路径
			String className = (String) handlerOrClassName;
			try {
				// 5.通过反射,根据类路径拿到handler
				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");
				}
				// 6.初始化
				NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
				// 7.调用自定义的namespaceHandler初始化方法
				/**
				 * public class UserNamespaceHandler extends NamespaceHandlerSupport {
				 *    @Override
				 *    public void init() {
				 * 		registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
				 *    }
				 * }
				 */
				namespaceHandler.init();
				// 8.记录在缓存中
				handlerMappings.put(namespaceUri, namespaceHandler);
				return namespaceHandler;
			}
			// ...catch
		}
	}
}

上述代码很好理解,无非就是根据我们Spring.handlers配置文件中的映射地址,去找到对应的Handler,通过反射去调用其初始化方法,来提取自定义标签处理器。 那么Spring是如何读取配置文件的呢?我们来看下第一步代码

Map<String, Object> handlerMappings = getHandlerMappings();
↓↓↓↓↓↓
private Map<String, Object> getHandlerMappings() {
	// 1.如果没有被缓存则开始缓存
	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 {
					// this.handlerMappingsLocation在构造函数调用的时候就会初始化为META-INF/Spring.handlers
					Properties mappings =
							PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
					if (logger.isTraceEnabled()) {
						logger.trace("Loaded NamespaceHandler mappings: " + mappings);
					}
					handlerMappings = new ConcurrentHashMap<>(mappings.size());
					// 将Properties格式文件合并到map中
					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;
}

我们来看下其构造:

public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
↓↓↓↓↓↓
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) {
	this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
↓↓↓↓↓↓
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader, String handlerMappingsLocation) {
	Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
	this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
	this.handlerMappingsLocation = handlerMappingsLocation;
}

可见我们在固定的文件目录下创建固定名称的文件是有意义的(META-INF/spring.handlers

2.2.2 标签解析

上述代码中,我们只讲完了Handler的初始化工作,还没有讲其解析的工作,我们来看下第三步:

return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));

代码定位到NamespaceHandlerSupport类中:

public abstract class NamespaceHandlerSupport implements NamespaceHandler {
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		// 寻找解析器并进行解析操作
		BeanDefinitionParser parser = findParserForElement(element, parserContext);
		return (parser != null ? parser.parse(element, parserContext) : null);
	}
}

解析的第一步肯定是找到对应的解析器,而我们Handler在初始化的时候,注册了自己的解析器UserBeanDefinitionParser

public void init() {
	registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}

我们来看下findParserForElement方法:

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
		// 1.获取元素名称,也就是<myname:user中的user
		String localName = parserContext.getDelegate().getLocalName(element);
		// 2.根据user找到对应的解析器,对应初始化代码registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
		BeanDefinitionParser parser = this.parsers.get(localName);
		if (parser == null) {
			parserContext.getReaderContext().fatal(
					"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
		}
		return parser;
	}

最后对于解析的处理,定位到AbstractBeanDefinitionParser类:

public abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser {
	public final BeanDefinition parse(Element element, ParserContext parserContext) {
		// 1.做一系列的数据准备,对beanClass、scope、lazyInit等属性的准备
		AbstractBeanDefinition definition = parseInternal(element, parserContext);
		if (definition != null && !parserContext.isNested()) {
			try {
				// 解析id
				String id = resolveId(element, definition, parserContext);
				if (!StringUtils.hasText(id)) {
					parserContext.getReaderContext().error(
							"Id is required for element '" + parserContext.getDelegate().getLocalName(element)
									+ "' when used as a top-level tag", element);
				}
				// 解析别名
				String[] aliases = null;
				if (shouldParseNameAsAliases()) {
					String name = element.getAttribute(NAME_ATTRIBUTE);
					if (StringUtils.hasLength(name)) {
						aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
					}
				}
				// 将解析出来的结果存于holder实例,并注册BeanDefinition
				BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
				registerBeanDefinition(holder, parserContext.getRegistry());
				if (shouldFireEvents()) {
					// 通知监听器进行处理
					BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
					postProcessComponentDefinition(componentDefinition);
					parserContext.registerComponent(componentDefinition);
				}
			}
			catch (BeanDefinitionStoreException ex) {
				String msg = ex.getMessage();
				parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
				return null;
			}
		}
		return definition;
	}
}

这里就和上文的默认标签解析的流程大致相同了,解析Bean属性、用holder存放数据、再注册等等。文章到这里也就讲完了。讲到这里,Spring对标签(默认、自定义)的解析、处理都已经结束。而Spring中的全部解析工作也已经完成,即:bean从配置文件到加载到内存的全部过程。

三. 大总结☆

Spring对标签的解析分为两种:

  1. 默认标签解析(Spring原生自带的)parseDefaultElement方法。
  2. 自定义标签解析parseCustomElement方法。

本文对标签的解析是在Spring读取解析好XML配置并将其转化为Document对象之后。通过解析Document对象来解析标签获得可用的BeanDefinition

parseDefaultElement流程(一共4种:<import><alias><bean><beans>),以最常见的<bean>标签的解析为例,

在这里插入图片描述

下一篇文章准备学习下bean的加载。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-03-06 12:48:23  更:2022-03-06 12:49:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:25:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码