Spring自定义标签

image-20210708133243955

本文学习自马士兵MAC课程-源码五班

添加自定义标签步骤

添加 xsd(XML Schema Definition) 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://eitan-blog.github.io/schema/eitan"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:element name="user">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="userName" type="xsd:string"/>
<xsd:attribute name="password" type="xsd:string"/>
<xsd:attribute name="email" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
  1. xmlns:xsd=”http://www.w3.org/2001/XMLSchema":表示该引用该命名空间的编写规范
  2. targetNamespace=”http://eitan-blog.github.io/schema/eitan":表示当前命名空间的地址
  3. elementFormDefault=”qualified” attributeFormDefault=”unqualified”:表示使用该命名空间的规范时,元素必须使用前缀短名,而属性可以不用,如 **<eitan:user id=”user1” userName=”eitan” email=”eitan_blog@163.com“ password=”123456”></eitan:user>eitan 就是前缀短名,user* 是元素,*userName 是属性

添加 spring.handlers

1
http\://eitan-blog.github.io/schema/eitan=com.eitan.tag.handler.UserNamespaceHandler
  1. http://eitan-blog.github.io/schema/eitan:表示你的命名空间
  2. com.eitan.tag.handler.UserNamespaceHandler:表示你对该命名空间的处理器的全限定类名

添加 Spring.shemas

1
http\://eitan-blog.github.io/schema/eitan/user.xsd=com.eitan.tag/user.xsd

编写命名空间映射关系,目的是为了在 xml 中找到映射对于的 xsd 文件

编写 UserNamespaceHandler

1
2
3
4
5
6
public class UserNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("user", new UserBeanDefinitionPaser());
}
}

该类主要是用于初始化 xml 每一个元素对应的解析类

编写 UserBeanDefinitionPaser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class UserBeanDefinitionPaser extends AbstractSingleBeanDefinitionParser {

// 返回属性值所对应的对象
@Override
protected Class<?> getBeanClass(Element element) {
return User.class;
}

@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
// 获取标签对应的属性值
String userName = element.getAttribute("userName");
String email = element.getAttribute("email");
String password = element.getAttribute("password");
if (StringUtils.hasText(userName)) {
builder.addPropertyValue("userName", userName);
}
if (StringUtils.hasText(email)) {
builder.addPropertyValue("email", email);
}
if (StringUtils.hasText(password)) {
builder.addPropertyValue("password", password);
}
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:eitan="http://eitan-blog.github.io/schema/eitan"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://eitan-blog.github.io/schema/eitan http://eitan-blog.github.io/schema/eitan/user.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<eitan:user id="user1" userName="eitan" email="eitan_blog@163.com" password="123456"></eitan:user>

<context:property-placeholder location="classpath*:db.properties"></context:property-placeholder>
<bean id="person" class="com.eitan.quickstart.entity.Person">
<property name="id" value="1"></property>
<property name="name" value="zs"></property>
</bean>
</beans>
  1. 在文件中引入自己的 xsd
    1. xmlns:eitan=”http://eitan-blog.github.io/schema/eitan"
    2. xsi:schemaLocation=http://eitan-blog.github.io/schema/eitan http://eitan-blog.github.io/schema/eitan/user.xsd
  2. 在文件中使用自己的标签:*<eitan:user id=”user1” userName=”eitan” email=”eitan_blog@163.com“ password=”123456”></eitan:user>*

效果

1
2
3
4
5
6
7
public class MyApplicationContext {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-${username}.xml");
User user = ac.getBean(User.class);
System.out.println(user);
}
}

MyApplicationContext 中获取并打印 User.class,可以发现写在 xml 中的属性成功注入进 user

Connected to the target VM, address: ‘127.0.0.1:12821’, transport: ‘socket’
User{userName=’eitan’, email=’eitan_blog@163.com‘, password=’123456’}

源码解析

  1. 该过程发生在 AbstractApplicationContextrefresh() 方法中
  2. refresh()obtainFreshBeanFactory()loadBeanDefinitions(beanFactory) 重载方法里
  3. doLoadBeanDefinitions(inputSource, encodedResource.getResource())registerBeanDefinitions(doc, resource) 方法中,调用 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)) 方法完成对 Document 对象完成具体的解析过程
  4. 核心处理代码在 doRegisterBeanDefinitions(doc.getDocumentElement()) 的 parseBeanDefinitions(root, this.delegate) 里

parseBeanDefinitions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// 解析 import、alias、bean 标签
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
  1. 这里是判断元素是默认命名空间的元素 delegate.isDefaultNamespace(ele)
  2. 我们这里现在不是默认的命名空间,所以调用的是解析自定义元素的方法 delegate.parseCustomElement(ele)

parseCustomElement

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// 获取对应的命名空间
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 根据命名空间找到对应的NamespaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 调用自定义的NamespaceHandler进行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
  1. 我们先从上下文中获取对应的 handler
  2. 再调用 handlerparse(Element element, ParserContext parserContext) 方法

resolver 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public NamespaceHandler resolve(String namespaceUri) {
// 获取所有已经配置好的handler映射
Map<String, Object> handlerMappings = getHandlerMappings();
// 根据命名空间找到对应信息
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
// 如果已经做过解析,handlerMappings中存放的就是对应的处理器对象,直接返回
return (NamespaceHandler) handlerOrClassName;
}
else {
// 如果没有做过解析,handlerMappings.get(namespaceUri)则返回的是类路径
String className = (String) handlerOrClassName;
try {
// 通过反射将类路径转化为类
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
// 实例化类
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 调用自定义namespaceHandler的初始化init()方法
namespaceHandler.init();
// 将结果记录到缓存中
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
"] for namespace [" + namespaceUri + "]", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
className + "] for namespace [" + namespaceUri + "]", err);
}
}
}

映射关系是什么时候加载到程序中的

首先再创造上下文对象的时候会对路径赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 对Document对象完成具体的解析过程
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}

public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}

public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}

protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
return new DefaultNamespaceHandlerResolver(cl);
}

public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) {
this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}

public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

在 resolve 方法时会调用 getHandlerMappings() 获取对应的映射关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private Map<String, Object> getHandlerMappings() {
Map<String, Object> handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
synchronized (this) {
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
if (logger.isTraceEnabled()) {
logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
}
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isTraceEnabled()) {
logger.trace("Loaded NamespaceHandler mappings: " + mappings);
}
handlerMappings = new ConcurrentHashMap<>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
}
}
}
}
return handlerMappings;
}

趣事:在 Debug 中,代码没有调用 getHandlerMappings() 方法,但是我们却能看到对应的映射关系

原因是 Debug 过程中,IDEA 自动调用了 toString() 方法,而 toString() 方法中有 getHandlerMappings()

1
2
3
4
@Override
public String toString() {
return "NamespaceHandlerResolver using mappings " + getHandlerMappings();
}

小问题-为什么 Spring.schemas 文件开头要大写

当该文件改成 spring.schemas时,gradle 会报错

1
2
3
4
5
Cause: assert shortName != key
| | |
| | 'http://eitan-blog.github.io/schema/eitan/user.xsd'
| false
'http://eitan-blog.github.io/schema/eitan/user.xsd'

原因是发生在 gradle 文件夹下面的 docs.gradle 的注释代码,注释掉后不管大小写都能正常运行。

image-20210713094814309