Spring源码系列:标签的解析原理
前言
在了解了Spring容器加载资源的一个过程后,其对资源加载的最后一步则是生成对应的BeanDefinition
,在本篇文章,我们着重讲Spring是如何对配置文件的标签进行解析的流程,parseDefaultElement
和parseCustomElement
方法。
先从parseDefaultElement
开始:
一. 默认标签的解析
默认标签的解析,会分别对4种不同的标签做处理:
import
:importBeanDefinitionResource()
alias
:processAliasRegistration()
bean
:processBeanDefinition()
beans
:doRegisterBeanDefinitions()
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;
}
}
简而言之就是:
- 解析
id
和name
属性,并对name
属性进行分割。 - 解析其他属性。若
beanName
没有,则自动生成个。 - 将上述获取到结果的再进行整合,得到最终的
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;
}
这里的解析其他属性,也就是解析class
、description
、property
等属性。我们先来看下,用于承载这些属性的GenericBeanDefinition
对象:
(1) GenericBeanDefinition对象
BeanDefinition
是一个接口,他是配置文件<bean>
元素标签在容器内部的一个表现形式。 例如:<bean>
标签拥有class
、scope
、lazy-init
等配置属性。而BeanDefinition
则提供了对应的beanClass
、scope
、lazyInit
操作方法。其为一一对应的一个关系。
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
实例:LookupOverride
与ReplaceOverride
。用于记录相关参数。而其最终都是记录在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
属性。
两种情况的前几步骤是一样的:
- 解析
Constructor-arg
的子元素。 - 使用
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
标签的时候就已经讲过了,无非就是解析ref
、value
以及子元素(set
、map
等标签)的过程,这里也就不做展开。
最终解析好的属性则会记录在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;
}
- 获取属性或元素的命名空间。
- 判断该元素是否适用于自定义标签解析的条件。
- 找到自定义类型对应的
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
的注册分成了两个部分:
- 通过beanName的注册。
- 通过别名的注册。
(1) 通过beanName注册
整体而言,我们都将直到Spring容器中存储BeanDefinition
的方式就是放在map
中,beanName
则作为map
的key
。具体的我们来看下:
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();
}
}
总结下就是:
- 校验
methodOverrides
属性。 - 处理
beanName
被注册但是不允许被覆盖的情况。 - 加入
map
进行注册。 - 清除缓存。
(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
标签的解析和注册已经结束,但这个仅仅是针对默认的标签解析,而其他的allias
、import
、beans
标签,本文不再展开描述。
二. 自定义标签的解析
讲完parseDefaultElement
方法后,也就是默认标签的解析流程。我们接下来去了解下自定义标签的使用和解析原理parseCustomElement
方法。
2.1 自定义标签的运用
扩展一个自定义标签的配置大概需要这么几个步骤:
- 创建一个需要扩展的组件。
- 定义一个
XSD
文件描述组件的内容。 - 创建一个文件,实现
BeanDefinitionParser
接口,用来解析XSD
文件中的定义和组件定义。 - 创建一个
Handler
文件,扩展自NamespaceHandlerSupport
,用于将组件注册到Spring容器中。 - 编写
Spring.handlers
和Spring.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.handlers
和Spring.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
配置Bean
。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: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加载自定义标签的流程大概是这样:
- 遇到自定义标签后,就去
Spring.handlers
和Spring.schemas
文件中去找到对应的解析handler
和XSD
。默认位置是/META-INF/
下。 - 找对于的
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对标签的解析分为两种:
- 默认标签解析(Spring原生自带的)
parseDefaultElement
方法。 - 自定义标签解析
parseCustomElement
方法。
本文对标签的解析是在Spring读取解析好XML
配置并将其转化为Document
对象之后。通过解析Document
对象来解析标签获得可用的BeanDefinition
。
parseDefaultElement
流程(一共4种:<import>
、<alias>
、<bean>
、<beans>
),以最常见的<bean>
标签的解析为例,
下一篇文章准备学习下bean
的加载。