前言
在上一篇 一文带你深入理解Spring中的xml解析过程与BeanDefinition的注册 中介绍了xml的解析配置和默认标签的解析过程。对应下图的左侧部分,本篇将完成右侧部分自定义标签的解析过程分析,并尝试实现一个我们自定义的标签。
解析自定义标签
命名空间处理器Handler
我们直接定位到parseCustomElement()方法所在的位置
# BeanDefinitionParserDelegate
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// 获取命名空间URI
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 获取该URI对应的Handler
// resolve方法会通过反射实例化一个NamespaceHandler, 返回该实例, 同时将其放到一个Map中缓存起来, 在下次用到的时候直接从该缓存中获取
// 这里的readerContext对象, 是在XmlBeanDefinitionReader类的registerBeanDefinitions()方法中创建的
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 委托给 Parser解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
上面的方法完成了三件事:
- 获取命名空间Uri
- 根据Uri获取对应的命名空间处理器Handler,并缓存:NamespaceHandler
- 通过处理器对自定义标签,进行解析处理
下面来看第二步中的resolve(namespaceUri)这个方法
# DefaultNamespaceHandlerResolver
public NamespaceHandler resolve(String namespaceUri) {
// 获取一个handlerMapping
// map形如:http://www.springframework.org/schema/context -> org.springframework.context.config.ContextNamespaceHandler
// 对于context这个自定义的标签来说, 一共注册了9个处理器handler, 每个处理器在init()方法中又注册了一个或多个处理不同标签元素的解析器parser
Map<String, Object> handlerMappings = getHandlerMappings();
// 通过namespaceUri获取到一个处理器handler对象, 实际上第一次拿到的只是写在配置文件中的字符串, 因此下面需要通过反射进行实例化
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
} else if (handlerOrClassName instanceof NamespaceHandler) {
// 如果handlerOrClassName是一个NamespaceHandler对象,则直接返回 - 拿到对应的handler了
// 说明不是第一次了
return (NamespaceHandler) handlerOrClassName;
} else {
// 如果handlerOrClassName不是NamespaceHandler对象,则是String对象
String className = (String) handlerOrClassName;
try {
// 通过String获取到一个Class对象,那么这个String对象肯定是一个类的全限定名啦
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("// 省略");
}
// 直接通过反射, 调用的无参构造器, 构造一个实例
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 初始化, 注册一个或多个与该自定义标签对应BeanDefinitionParser, 将其存入到HashMap中: —> this.parsers.put(elementName, parser);
// init中的方法如:registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser())
// 接下来的步骤会根据自定义标签再去获取相对应的解析器
namespaceHandler.init();
// 把新创建的namespaceHandler, 再存储到handlerMappings中
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
} catch (Exception ex) {
// 省略
}
}
}
该方法其实做了两件事:
- 实例化一个处理器NamespaceHandler,并进行缓存相关的优化处理
- 调用namespaceHandler.init(),其实该方法是为NamespaceHandler注册了一系列的解析器。自定义的xml的标签属性等,就是通过这些解析器完成解析的。 我们来逐行分析这段函数的细节:
第一行调用了 getHandlerMappings() 方法得到了一个Map,我们来跟进这个方法:
# DefaultNamespaceHandlerResolver
private Map<String, Object> getHandlerMappings() {
Map<String, Object> handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
synchronized (this) {
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
try {
// 默认一共加载了三个Spring源码模块的META-INF/spring.handlers文件里指定的处理器hanlder, 总共是9个
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
handlerMappings = new ConcurrentHashMap<>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
// 省略异常
}
}
}
}
return handlerMappings;
}
我们来分析上面的代码:第一行中,将属性handlerMappings赋值给方法的局部变量,其值实际为空,所以进入try逻辑处理块。
重点来分析PropertiesLoaderUtils的loadAllProperties这个静态方法:
- 我们先看第一个参数handlerMappingsLocation, 该参数是如何赋值进来的呢?
在前面简单提到过readerContext的初始化时机,在XmlBeanDefinitionReader.registerBeanDefinitions(...)方法中,调用了documentReader.registerBeanDefinitions(doc, createReaderContext(resource))方法,其中的createReaderContext方法,最后调用了DefaultNamespaceHandlerResolver的构造参数,并给了默认值 "META-INF/spring.handlers"
loadAllProperties代码如下,该方法是一个加载Properties的工具方法,属性文件的位置已经通过参数赋值进去,即上面所说的"META-INF/spring.handlers"
public static Properties loadAllProperties(String resourceName, @Nullable ClassLoader classLoader) throws IOException {
Assert.notNull(resourceName, "Resource name must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = ClassUtils.getDefaultClassLoader();
}
// 默认spring中一个有三处, 分别是:spring-context有5项, spring-aop有1项, spring-beans有3项
// 因此一共加载了9相, 并在调用的地方, 放在了map中
Enumeration<URL> urls = (classLoaderToUse != null ? classLoaderToUse.getResources(resourceName) :
ClassLoader.getSystemResources(resourceName));
Properties props = new Properties();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
URLConnection con = url.openConnection();
ResourceUtils.useCachesIfNecessary(con);
try (InputStream is = con.getInputStream()) {
if (resourceName.endsWith(XML_FILE_EXTENSION)) {
props.loadFromXML(is);
}
else {
props.load(is);
}
}
}
return props;
}
至此,getHandlerMappings() 方法就分析完了,该方法返回了在resources目录下的"META-INF/spring.handlers"文件中定义的NamespaceHandler。默认一共是9个。
这里返回的只是一个全类名的字符串,下面,还需要进行实例创建和缓存。回来继续来看DefaultNamespaceHandlerResolver。
# DefaultNamespaceHandlerResolver
public NamespaceHandler resolve(String namespaceUri) {
// 获取一个handlerMapping
// map形如:http://www.springframework.org/schema/context -> org.springframework.context.config.ContextNamespaceHandler
// 对于context这个自定义的标签来说, 一共注册了9个处理器handler, 每个处理器在init()方法中又注册了一个或多个处理不同标签元素的处理器parser
Map<String, Object> handlerMappings = getHandlerMappings();
// 通过namespaceUri获取到一个处理器handler对象, 实际上此时拿到的只是写在配置文件中的字符串, 因此下面需要通过反射进行实例化
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
// 如果handlerOrClassName是一个NamespaceHandler对象,则直接返回 - 拿到对应的handler了
return (NamespaceHandler) handlerOrClassName;
}
else {
// 如果handlerOrClassName不是NamespaceHandler对象,则是String对象
String className = (String) handlerOrClassName;
try {
// 通过String获取到一个Class对象,那么这个String对象肯定是一个类的全限定名
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
// 直接通过反射, 调用的无参构造器, 构造一个实例
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 初始化, 注册一个或多个与该自定义标签对应BeanDefinitionParser, 将其存入到HashMap中: —> this.parsers.put(elementName, parser);
// init中的方法如:registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser())
// 接下来的步骤会根据自定义标签再去获取相对应的解析器
namespaceHandler.init();
// 把新创建的namespaceHandler, 再存储到handlerMappings中
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
"] for namespace [" + namespaceUri + "]", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
className + "] for namespace [" + namespaceUri + "]", err);
}
}
}
上面的代码, 在namespaceHandler.init()方法逻辑之前, 主要完成了将从配置文件中获取到的Handler实例化成对象,同时放入缓存,方便下次获取。
再来看init()方法,该方法源自NamespaceHandler接口,如下:
public interface NamespaceHandler {
/**
* 初始化解析器
*/
void init();
/**
* 解析自定义的标签元素
*/
BeanDefinition parse(Element element, ParserContext parserContext);
BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);
}
spring 还为我们提供了 NamespaceHandlerSupport
类,该类抽象实现了NamespaceHandler的方法,一般自定义的Handler继承自该抽象类即可。源码如下。
public abstract class NamespaceHandlerSupport implements NamespaceHandler {
/**
* 用来缓存解析器, key为自定义标签的前缀, 如<component:scan>中的 "component"
*/
private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 先获取到element这个自定义标签对应的namespaceURI, 再从map中根据这个key获取相应的解析器
// 这个map是在实例化NamespaceHandler之后, 在调用它的init()方法的时候, 里面调用了多个registerBeanDefinitionParser方法, 将其存入到map中的
BeanDefinitionParser parser = findParserForElement(element, parserContext);
// 再委托给获取到的解析器parser去解析这个自定义标签
return (parser != null ? parser.parse(element, parserContext) : null);
}
/**
* 返回对应的自定义标签的解析器
*/
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
//去掉标签前面的前缀, 如:context:component-scan ——> component-scan
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
@Override
public BeanDefinitionHolder decorate(
Node node, BeanDefinitionHolder definition, ParserContext parserContext) {
BeanDefinitionDecorator decorator = findDecoratorForNode(node, parserContext);
return (decorator != null ? decorator.decorate(node, definition, parserContext) : null);
}
// 省略其他方法和属性
}
我们以component这个自定义标签为例,来看下具体的解析过程:
先给出测试代码:
xml配置文件:
<context:component-scan base-package="com.context.basic.custom_xml_element.basic"/>
Java代码:
com.context.basic
public class App {
public static void main( String[] args ) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:custom_xml_element.xml");
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
}
}
package com.context.basic;
@Component
public class Person {
public Person() {
System.out.println("Person init...");
}
}
基于上面这段代码,继续跟踪DefaultNamespaceHandlerResolver.resolve方法中的调用:namespaceHandler.init();跟踪进入,源码如下:
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// 第一个参数分别对应于context下的不同的标签, 第二个实例对象, 用来解析与之对应的标签元素
// 如 <context:property-placeholder/>、<context:annotation-config/> 等
// 其实这里就是一行代码, 将标签对应的解析器, 放到一个map中, 定义如下:Map<String, BeanDefinitionParser> parsers
// 当后面解析某个自定义的标签元素时. 直接通过key从这个map中获取对应的解析器进行解析即可
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}
这段代码,注册了context命名空间下,不同的标签元素的解析。
这里先分析下NamespaceHandler
这个类,该类在上面见到过,它是命名空间处理器的跟接口, 定义的规范如下:
public interface NamespaceHandler {
/**
* 初始化解析器,这个方法将会在NamespaceHandler实例化之后,使用之前调用
*/
void init();
/**
* 完成元素标签的解析工作,并注册成BeanDefinition中,以便beanFactory完成实例等工作
* 真正的解析工作是在自定义的元素解析器中完成的,这里只是从注册的解析器中,获取到对应的属性解析器, 来完成解析工作的
*/
@Nullable
BeanDefinition parse(Element element, ParserContext parserContext);
/**
* 对BeanDefinition进行装饰
*/
@Nullable
BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);
}
NamespaceHandlerde
的抽象实现类:NamespaceHandlerSupport
,我们重点来看解析和初始化相关的源码,装饰部分不贴出来了。
public abstract class NamespaceHandlerSupport implements NamespaceHandler {
// 用来存储某个命名空间的所有xml标签属性的解析器对
private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 先获取到element这个自定义标签对应的namespaceURI, 再从map中根据这个key获取相应的解析器
// 这个map是在实例化NamespaceHandler之后, 在调用它的init()方法的时候, 里面调用了多个registerBeanDefinitionParser方法, 将其存入到map中的
BeanDefinitionParser parser = findParserForElement(element, parserContext);
// 再委托给获取到的解析器parser去解析这个自定义标签
return (parser != null ? parser.parse(element, parserContext) : null);
}
/**
* 返回对应的自定义标签的解析器
*/
@Nullable
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
//去掉标签前面的前缀, 如:context:component-scan ——> component-scan
String localName = parserContext.getDelegate().getLocalName(element);
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
}
通过上面给出的NamespaceHandlerSupport源码,可以总结主要完成两件事(装饰部分,本文不说明):
- 注册Parser解析器, 并缓存到parsers中获取的相匹配的解析器, 完成解析, 并封装成变量中。由于该操作是通过其中的registerBeanDefinitionParser完成的,所有在我们自定义标签时候, 需要调用该方法(在自定义的NamespaceHandler的init方法中调用, 文末会给出一个自定义标签的示例)。
- 通过parsers中获取的相匹配的解析器, 完成解析, 并封装成BeanDefinition对象返回。
属性解析器Parser
最后我们再来分析元素标签的属性解析器BeanDefinitionParser
在自定义handler的init方法中注册了自定义的属性解析器,下面来分析该接口
public interface BeanDefinitionParser {
@Nullable
BeanDefinition parse(Element element, ParserContext parserContext);
}
该接口只定义了parse这一个方法,该方法完成将xml中的元素属性,封装为BeanDefinition的功能。 来看下该接口的抽象实现类:
public abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser {
public static final String ID_ATTRIBUTE = "id";
public static final String NAME_ATTRIBUTE = "name";
@Override
@Nullable
public final BeanDefinition parse(Element element, ParserContext parserContext) {
AbstractBeanDefinition definition = parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
try {
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));
}
}
// 将Bean的定义信息、id、别名包装到holder类中
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
// 进行BeanDefinition的注册工作
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;
}
protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException {
if (shouldGenerateId()) {
return parserContext.getReaderContext().generateBeanName(definition);
} else {
String id = element.getAttribute(ID_ATTRIBUTE);
if (!StringUtils.hasText(id) && shouldGenerateIdAsFallback()) {
id = parserContext.getReaderContext().generateBeanName(definition);
}
return id;
}
}
protected void registerBeanDefinition(BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {
BeanDefinitionReaderUtils.registerBeanDefinition(definition, registry);
}
@Nullable
protected abstract AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext);
protected boolean shouldGenerateId() {
return false;
}
protected boolean shouldGenerateIdAsFallback() {
return false;
}
protected boolean shouldParseNameAsAliases() {
return true;
}
protected boolean shouldFireEvents() {
return true;
}
protected void postProcessComponentDefinition(BeanComponentDefinition componentDefinition) {
}
}
注意里面的模板方法parseInternal,该方法留给子类实现的。在下面我们自定义一个标签的属性解析器的时候,使用的AbstractSingleBeanDefinitionParser,就继承了AbstractBeanDefinitionParser。
自定义标签解析总结
- 获取META-INF/spring.handlers文件定义的所有handler。
- 优先通过缓存获取, 获取不到, 则根据第一步找的进行反射创建, 并缓存。
- 调用handler的init方法, 在init中调用其父类NamespaceHandlerSupport中的。registerBeanDefinitionParser方法,将自定义的标签元素解析器注册到map中。
- 完成上述工作之后, 准备解析。
实际上,这就是Spring中处理自定义标签的核心流程。后续的parse这里先不详细说明,大概流程就是使用前面初始化好的handler和parsers, 根据不同的属性从map中获取不同的parser,来完成解析工作。
额外的多说一句,context还可以使用过滤器,context中默认有自己的过滤器,也可以自定义规则,有两种include-filter和exclude-filter,符合过滤器规则的,才会被实例。
如:关闭默认过滤器的情况下,只有在base-package包下的Service才会被注入到spring容器中。
具体的源码在ComponentScanBeanDefinitionParser.configureScanner(..)获取扫描器的时候会调用parseTypeFilters(),实现该功能,有兴趣可以自己跟踪。
<context:component-scan base-package="com.custom_xml_element.filter" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
实现一个自定义的spring标签元素
根据上面的源码分析,我们大致知道,需要准备几样东西,
- META-INF/spring.handlers中指定命名空间处理器的全路径、及对应的实现类,这里该类继承自
NamespaceHandlerSupport
- 自定义标签元素的解析器,这里继承自
AbstractSingleBeanDefinitionParser
抽象类即可 - 定义好相关的xml约束文件
1. Handler类:
package com.mystrace.selftag;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
public class FiverNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// 对应于配置文件中的后半部分:<fiver:mybean>
registerBeanDefinitionParser("mybean", new FiverSingleBeanDefinitionParser());
}
}
- parser类:
package com.mystrace.selftag;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
public class FiverSingleBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return User.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String name = element.getAttribute("name");
String password = element.getAttribute("password");
if (StringUtils.hasText(name)) {
builder.addPropertyValue("name", name);
}
if (StringUtils.hasText(password)) {
builder.addPropertyValue("password", password);
}
}
}
- 普通类
package com.mystrace.selftag;
public class User {
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
- 测试类
package com.mystrace.selftag;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AppSelfTag {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:self_tag.xml");
User user = (User) context.getBean("user");
System.out.println("name = " + user.getName() + ", password = " + user.getPassword());
}
}
- spring配置文件self_tag.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:fiver="http://www.fiver.com/schema/user" #要和下面的spring.handlers中的key一致
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.fiver.com/schema/user
http://www.fiver.com/schema/user.xsd"> #要和下面的user.xsd文件名称一致,且域名一致
<fiver:mybean id="user" name="fiver" password="123456"/>
</beans>
- 在resources/META-INF目录下创建文件spring.handlers 内容如下:
http\://www.fiver.com/schema/user=com.mystrace.selftag.FiverNamespaceHandler
其中左侧的key,要和spring配置文件中beans节点中的xmlns:fiver对应的值一致,右边的值即为Handler的全类名
- 在user.xsd(xml定义文件)
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.fiver.com/schema/user" #注意这里定义了的命名空间
xmlns:tns="http://www.fiver.com/schema/user"
elementFormDefault="qualified">
<element name="mybean">
<complexType>
<attribute name ="id" type = "string"/>
<attribute name ="name" type = "string"/>
<attribute name ="password" type = "string"/>
</complexType>
</element>
</schema>
至此,Spring中关于默认标签和自定义标签的解析已经梳理完毕。
默认标签和自定义标签的解析总结
我们以一张图来总结相关的源码解析过程,希望对读者有所帮助。