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

前言

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的源码,能够带给我们很大的启发。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值