Spring自定义标签
一、Spring配置文件中xml中标签的解析过程
在聊Spring的xml中配置文件,我们首先需要关注三个文件:
spring.handlers
、spring.schmas
以及在schmas
文件中对应位置的xsd
文件
xsd文件定义了在对应的命名空间下有什么标签,标签对应的属性及属性对于的数据类型
spring.handlers
、spring.schmas
会在 AbstactXmlApplicationContext中的loadBeanDefinitions中被加载,接下来我们重点聊一下这个方法。
1. loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
创建一个xml的BeanDefinitionReader,通过回调设置到BeanFactory中
给reader对象设置环境对象,需要重点关注
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
这个方法将ResourceEntityResolver
对象设置进入BeanDefinitionReader中,而在ResourceEntityResolver
的父类构造中,将dtd的解析器和schema的解析器PluggableSchemaResolver
类型的属性赋值。而在PluggableSchemaResolver
类中的getSchemaMappings()
会将网络上dtd地地址和对应的本地dtd文件生成map映射关系。而该方法的实际调用位置是DefaultNameSpaceHandlerResolver.resolve(String namespaceUri)
中显示调用;而在IDEA调试时,我们往往会发现这个方法过早的被调用。这是由于IDEA中的调试会隐式调用该类的toString()方法,而在
PluggableSchemaResolver.toString()
会调用getSchemaMappings()
,导致了这个结果[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dk82qg65-1623206811865)(D:\md文档\images\image-20210527114951894.png)]
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 创建一个xml的beanDefinitionReader,并通过回调设置到beanFactory中
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 给reader对象设置环境对象
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 初始化beanDefinitionReader对象,此处设置配置文件是否要进行验证
initBeanDefinitionReader(beanDefinitionReader);
// 开始完成beanDefinition的加载
loadBeanDefinitions(beanDefinitionReader);
}
2. resolve(String namespaceUri)
方法位于
DefaultNamespaceHandlerResolver
类中,根据将xml配置文件读取成的Document中获取的namespace从而获取对应的NamespaceHandler
,而该命名空间下往往有多个BeanDefinitionParser
对应着不同的标签,根据这些解析类可将对应的标签解析成相应的BeanDefinition
。
@Override
@Nullable
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) {
// 如果已经做过解析,直接从缓存中读取
return (NamespaceHandler) handlerOrClassName;
}
else {
// 没有做过解析,则返回的是类路径
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的初始化方法
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);
}
}
}
具体调用链路如下所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PTpD159Y-1623206811866)(D:\md文档\images\image-20210528151613782.png)]
获取
NamespaceHandler
后调用handler.parse()
方法解析获取对应的BeanDefinition
二、自定义标签的方式
1. 在resources文件夹下建对应的文件
META-INF\spring.handlers
、META-INF\spring.schemas
、META-INF\user.xsd
spring.handlers
http\://www.tianya.com/schema/user=com.tianya.spring.selftag.UserNamespaceHandler
spring.schemas
http\://www.tianya.com/schema/user.xsd=META-INF/user.xsd
user.sxd
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.tianya.com/schema/user"
xmlns:tns="http://www.tianya.com/schema/user"
elementFormDefault="qualified">
<element name="user">
<complexType>
<attribute name="id" type="string"/>
<attribute name="userName" type="string"/>
<attribute name="email" type="string"/>
<attribute name="password" type="string"/>
</complexType>
</element>
</schema>
2. model对象
package com.tianya.spring.selftag.model;
/**
* ClassName: User <br/>
* Description: <br/>
* date: 2021/5/27 9:59<br/>
*
* @author tianya<br />
*/
public class User {
private String userName;
private String email;
private String password;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", email='" + email + '\'' +
", password='" + password + '\'' +
'}';
}
}
3. BeanDefinitionParse对象
package com.tianya.spring.selftag;
import com.tianya.spring.selftag.model.User;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* ClassName: UserBeanDefinitionParser <br/>
* Description: <br/>
* date: 2021/5/27 10:00<br/>
*
* @author tianya<br />
*/
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
/**
* 返回属性值所对应的对象
* @param element
* @return
*/
@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(password)){
builder.addPropertyValue("password", password);
}
if(StringUtils.hasText(email)) {
builder.addPropertyValue("email", email);
}
}
}
4. NamespaceHandler对象
package com.tianya.spring.selftag;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* ClassName: UserNamespaceHandler <br/>
* Description: <br/>
* date: 2021/5/27 9:21<br/>
*
* @author tianya<br />
*/
public class UserNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}
}
5. applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tianya="http://www.tianya.com/schema/user"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.tianya.com/schema/user http://www.tianya.com/schema/user.xsd">
<tianya:user id="tianya" email="1044916104@qq.com" password="123456" userName="wxp"></tianya:user>
</beans>
6.测试类
public class UserTagsTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) ac.getBean("tianya");
System.out.println(user);
}
}