spring源码解读系列(三):详解spring自定义标签

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 spring源码解读系列(三):详解spring自定义标签,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

前言

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文件

在这里插入图片描述
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

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!