前言
在spring源码解读系列(二)的最后我们发现了spring解析bean的方法分为两种,一种是解析spring工厂内部默认的标签,即import、alias、beans、bean;另一种是解析我们自定义的标签,本文详细分析spring解析自定义标签的过程,并带领大家自己实现一个自定义标签。
一、解析自定义标签入口parseCustomElement
方法作用:根据namespaceUri找到对应的NamespaceHandler实现类对象,然后调用parse方法对传入的标签元素进行解析
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// 获取对应的命名空间
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 根据命名空间找到对应的NamespaceHandler,这里默认调用DefaultNamespaceHandlerResolver.resolve
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));
}
这里
二、详解DefaultNamespaceHandlerResolver.resolve(namespaceUri);
该方法主要做两件事情:
1.通过getHandlerMappings()方法,将”META-INF/spring.handlers”路径下的文件读取到handlerMappings中
2.根据namespaceUri找到map中对应的NamespaceHandler实现类的类路径,通过反射创建实例对象,并返回
@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);
}
}
}
三、总结spring自定义标签整体流程
总结:
1.创建对应标签的解析类,即*BeanDefinitionParser类,继承AbstractSingleBeanDefinitionParser,并实现getBeanClass和doParse方法;创建处理类,继承NamespaceHandlerSupport,将实现的Parser类注册到spring工厂
2.在resources下创建META-INF/spring.handlers文件,将创建的Parser类路径和namespaceUri对应起来
3.在resources下创建META-INF/spring.schemas文件,将namespaceUri和对应的文件约束(.dtd或者.xsd文件)存放路径对应起来,并在存放路径下创建约束文件
四、实现自定义标签
我们按照上面总结的流程,自己实现一个简单的自定义标签ljx:test
1.创建对应标签的解析类和处理类
创建对应标签的解析类,解析标签属性
/**
* @author ljx
* @Description: 自定义标签的解析类
* @date 2021/8/3 10:09 上午
*/
public class LjxTestBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class<?> getBeanClass(Element element) {
return LjxTest.class;
}
/**
* 重写doParse方法,解析标签对应的属性
* @param element the XML element being parsed
* @param parserContext the object encapsulating the current state of the parsing process
* @param builder used to define the {@code BeanDefinition}
*/
@Override
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
if (element.hasAttribute("age")) {
builder.addPropertyValue("age", element.getAttribute("age"));
}
if (element.hasAttribute("name")) {
builder.addPropertyValue("name", element.getAttribute("name"));
}
if (element.hasAttribute("describe")) {
builder.addPropertyValue("describe", element.getAttribute("describe"));
}
}
}```
**标签对应的实体类**
```java
public class LjxTest {
private String name;
private Integer age;
private String describe;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescribe() {
return describe;
}
public void setDescribe(String describe) {
this.describe = describe;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
创建handler类,将解析类LjxTestBeanDefinitionParser注册到spring工厂
/**
* @author ljx
* @Description: 将自定义的BeanDefinitionParser注册spring工厂中
* @date 2021/8/3 10:46 上午
*/
public class LjxTestNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("test",new LjxTestBeanDefinitionParser());
}
}
2.在resources下创建META-INF/spring.handlers文件
# namespaceUri映射的LjxTestNamespaceHandler的类路径
http\://www.ljx.com/schema/test=com.mashibing.selftag.LjxTestNamespaceHandler
3.在resources下创建META-INF/spring.schemas文件
spring.schemas文件内容:
http\://www.ljx.com/schema/test.xsd=META-INF/test.xsd
自定义的test.xsd文件
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.ljx.com/schema/test"
xmlns:tns="http://www.ljx.com/schema/test"
elementFormDefault="qualified">
<element name="test">
<complexType>
<attribute name ="id" type = "string"/>
<attribute name ="age" type = "integer"/>
<attribute name ="name" type = "string"/>
<attribute name ="describe" type = "string"/>
</complexType>
</element>
</schema>
验证代码:
书写配置文件:
<?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:ljx="http://www.ljx.com/schema/test"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.ljx.com/schema/test http://www.ljx.com/schema/test.xsd">
<ljx:test id="ljxTest" name="ljx" describe="自定义标签" age="18" ></ljx:test>
</beans>
测试类:
public class TestSelfTag {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("customTag.xml");
LjxTest ljxTest=(LjxTest)context.getBean("ljxTest");
System.out.println("name:"+ljxTest.getName()+" "+"describe:"+ljxTest.getDescribe());
}
}
验证结果:
name:ljx describe:自定义标签
五、总结
spring自定义标签是一项非常强大和实用的功能,我们经常在其余框架中可以发现它的应用,比如dubbo中支持的dubbo相关标签,以及spring-context中的标签。实际开发中,我们项目一般都会依赖spring-context,当忘记自定义标签怎么实现时,可以参考spring-context的源码,能够带给我们很大的启发。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/130184.html