Spring实现自定义XML标签详解

Spring实现自定义XML标签详解前言 学习 spring 源码已经有一年多了 了解过的朋友肯定都知道 spring 源码是一块非常难啃的骨头 所以每当找到一丝丝成就感就想拿出来与大家一起分享 这样也能让自己始终保持着对 spring 源码学习的兴趣 虽然现在使用 xml 已经不是主流的方式了 但是一些公共的开源组件都基于自身的功能定制了自定义标签

大家好,我是讯享网,很高兴认识大家。

前言

学习spring源码已经有一年多了,了解过的朋友肯定都知道spring源码是一块非常难啃的骨头,所以每当找到一丝丝成就感就想拿出来与大家一起分享,这样也能让自己始终保持着对spring源码学习的兴趣。

虽然现在使用xml已经不是主流的方式了,但是一些公共的开源组件都基于自身的功能定制了自定义标签,比如dubbo。

自定义XML关键的几个配置

  • spring.handler
    定义解析xml元素的处理类。
  • spring.schemas
    指定xsd文件的位置。
  • xxx.xsd
    类似于语法规范,约束自定义标签的属性类型等。

具体实现

spring自身中其实也有很多自定义的标签,比如context、aop等,所以我们自己实现的方式很简单,照抄就可以了。

下面我们就按照context标签的实现方式来自己搞一个。

在这里插入图片描述
讯享网

1、建一个META-INF目录,并创建spring.handler和spring.schemas两个文件。

在这里插入图片描述

2、完成spring.handler文件中的内容

在这里插入图片描述

ContextNamespaceHandler点进去,继承了NamespaceHandlerSupport ,并重写init方法。

public class ContextNamespaceHandler extends NamespaceHandlerSupport { 
    @Override public void init() { 
    registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser()); registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser()); registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser()); registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser()); registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser()); registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser()); registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser()); registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser()); } } 

讯享网

照着实现即可,就定义一个标签user,创建了两个新的类,WylBeanDefinitionParser和User(user是指定标签生成的类)。

在这里插入图片描述

讯享网public class MyCustomNamespaceHandler extends NamespaceHandlerSupport { 
    @Override public void init() { 
    registerBeanDefinitionParser("user", new WylBeanDefinitionParser()); } } 
public class WylBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { 
    @Override protected Class<?> getBeanClass(Element element) { 
    return User.class; } @Override protected void doParse(Element element, BeanDefinitionBuilder builder) { 
    String name = element.getAttribute("name"); if (StringUtils.hasLength(name)) { 
    builder.addPropertyValue("name", name); } String age = element.getAttribute("age"); if (StringUtils.hasLength(age)) { 
    builder.addPropertyValue("age", age); } } } 
讯享网public class User { 
    private String name; private String age; public String getName() { 
    return name; } public void setName(String name) { 
    this.name = name; } public String getAge() { 
    return age; } public void setAge(String age) { 
    this.age = age; } } 

3、处理spring.schemas文件

这个文件很简单定义一下你的xsd文件的位置即可

在这里插入图片描述
在这里插入图片描述

4、处理xsd文件

这个就按照语法规范来实现就可以了

定义了标签名user,与前面MyCustomNamespaceHandler中的保持一致,并且定义了三个属性,id是标识。

<?xml version="1.0" encoding="UTF-8" standalone="no"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.springframework.org/schema/wyl" elementFormDefault="qualified"> <xsd:element name="user"> <xsd:complexType> <xsd:attribute name="id" type="xsd:string"> </xsd:attribute> <xsd:attribute name="name" type="xsd:string"> </xsd:attribute> <xsd:attribute name="age" type="xsd:string"> </xsd:attribute> </xsd:complexType> </xsd:element> </xsd:schema> 

5、使用

上面4步完成后,自定义标签就已经完成了,接下来只要在你的application.xml文件中添加自己的命名空间和schema位置就可以使用了

讯享网<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:wyl="http://www.springframework.org/schema/wyl" 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.springframework.org/schema/wyl http://www.springframework.org/schema/wyl.xsd "> <wyl:user id="myselfTag" name="wangwu" age="18"></wyl:user> </beans> 

6、测试

public class TestSpring { 
    public static void main(String[] args) { 
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml"); User user = (User) applicationContext.getBean("wyl"); System.out.println("myselfTag: " + user.getName()); } } 

在这里插入图片描述


源码分析

既然是学习源码,只照抄方法肯定是不够的,其实这部分逻辑还是比较清晰的,并且从头到尾我们自己重写的方法也就3个,一起来简单分析下吧。

1、MyCustomNamespaceHandler中init方法

讯享网public class MyCustomNamespaceHandler extends NamespaceHandlerSupport { 
    @Override public void init() { 
    registerBeanDefinitionParser("user", new WylBeanDefinitionParser()); } } 

这个init方法的调用链路比较长,入口肯定是从refresh方法中的obtainFreshBeanFactory()方法开始,最终会执行到如下的方法中,然后先从spring.handles文件中获取com.wyl.learn.config.MyCustomNamespaceHandler值(文件中等号右边定义的内容),再利用反射拿到Class对象,调用init方法即可。

 @Nullable public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { 
    String namespaceUri = getNamespaceURI(ele); if (namespaceUri == null) { 
    return null; } //init方法调用的入口 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { 
    error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } //init方法返回后,接着就处理parse方法调用的入口 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); } 
讯享网 public NamespaceHandler resolve(String namespaceUri) { 
    //获取所有的spring.handles文件中的内容,等号左边是key,等号右边的value Map<String, Object> handlerMappings = getHandlerMappings(); //根据key找到对象的value,也就是全限定类名,使用反射就可以得到具体的对象 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对象 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); //调用自己重写的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); } } } 

2、 WylBeanDefinitionParser中doParse方法

public class WylBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { 
    @Override protected Class<?> getBeanClass(Element element) { 
    return User.class; } @Override protected void doParse(Element element, BeanDefinitionBuilder builder) { 
    String name = element.getAttribute("name"); if (StringUtils.hasLength(name)) { 
    builder.addPropertyValue("name", name); } String age = element.getAttribute("age"); if (StringUtils.hasLength(age)) { 
    builder.addPropertyValue("age", age); } } } 

这个方法主要就是通过addPropertyValue把属性添加到beanDefinition对象的propertyValueList集合属性中。

讯享网 public BeanDefinitionBuilder addPropertyValue(String name, @Nullable Object value) { 
    this.beanDefinition.getPropertyValues().add(name, value); return this; } 

add方法

 public MutablePropertyValues add(String propertyName, @Nullable Object propertyValue) { 
    addPropertyValue(new PropertyValue(propertyName, propertyValue)); return this; } 

addPropertyValue方法

讯享网 public MutablePropertyValues addPropertyValue(PropertyValue pv) { 
    for (int i = 0; i < this.propertyValueList.size(); i++) { 
    PropertyValue currentPv = this.propertyValueList.get(i); if (currentPv.getName().equals(pv.getName())) { 
    pv = mergeIfRequired(pv, currentPv); setPropertyValueAt(pv, i); return this; } } //最终添加到propertyValueList集合中 this.propertyValueList.add(pv); return this; } 

propertyValueList是MutablePropertyValues类的属性,而MutablePropertyValues又是beanDefinition中的一个属性。

在这里插入图片描述

3、 最后还有一个getBeanClass方法

此方法返回的是你自定义标签的BeanClass对象,也就是User,容器在标签解析时就可以通过这个方法得到beanDefinition的class类型,最终通过doParse给属性赋完值以后就可以添加到容器中了,添加到容器中的对象之后就可以通过getBean方法获取了,属性可以从propertyValueList中获取。

小讯
上一篇 2025-04-02 11:34
下一篇 2025-02-08 15:12

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/52882.html