当前位置: 首页 > article >正文

【Spring详解四】自定义标签的解析

四、自定义标签的解析

自定义标签的解析是通过 BeanDefinitionParserDelegate .parseCustomElement(ele)进行的,解析来我们进行详细分析。
DefaultBeanDefinitionDocumentReader.class

4.1 自定义标签的使用

扩展 Spring 自定义标签配置一般需要以下几个步骤:
  • 创建一个需要扩展的组件
  • 定义一个 XSD 文件,用于描述组件内容
  • 创建一个实现 AbstractSingleBeanDefinitionParser 接口的类,用来解析 XSD 文件中的定义和组件定义
  • 创建一个 Handler,继承 NamespaceHandlerSupport ,用于将组件注册到 Spring 容器
  • 编写 Spring.handlersSpring.schemas 文件
下面就按照上面的步骤来实现一个自定义标签组件。

4.1.1 创建组件

该组件就是一个普通的 JavaBean,没有任何特别之处。这里我创建了两个组件,为什么是两个,后面有用到,代码如下:
package wangjiafu.spring01;

# User.java
public class User {
    private String id;
    private String userName;
    private String email;
    //省略get/set方法
}

# Phone.java
public class Phone {
    private String color;
    private int size;
    private String remark;
    //省略get/set方法
}

4.1.2 定义XSD文件

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

    <xsd:element name="phone">
        <xsd:complexType>
            <xsd:attribute name="id" type="xsd:string" />
            <xsd:attribute name="color" type="xsd:string" />
            <xsd:attribute name="size" type="xsd:int" />
            <xsd:attribute name="remark" type="xsd:string" />
        </xsd:complexType>
    </xsd:element>

</xsd:schema>
在上述XSD文件中描述了一个新的targetNamespace,并在这个空间里定义了一个name为user和phone的element 。user里面有三个attribute。主要是为了验证Spring配置文件中的自定义格式。再进一步解释,就是,Spring位置文件中使用的user自定义标签中,属性只能是上面的三种,有其他的属性的话,就会报错。

4.1.3 创建一个实现 AbstractSingleBeanDefinitionParser 接口的类

package wangjiafu.spring01;

# UserBeanDefinitionParser.java
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    @Override
    protected Class getBeanClass(Element ele){
        return User.class;
    }

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        String id = element.getAttribute("id");
        String userName=element.getAttribute("userName");
        String email=element.getAttribute("email");
        if(StringUtils.hasText(id)){
            builder.addPropertyValue("id",id);
        }
        if(StringUtils.hasText(userName)){
            builder.addPropertyValue("userName", userName);
        }
        if(StringUtils.hasText(email)){
            builder.addPropertyValue("email", email);
        }
    }
}

# PhoneBeanDefinitionParser.java
public class PhoneBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    @Override
    protected Class getBeanClass(Element ele){
        return Phone.class;
    }

    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        String color = element.getAttribute("color");
        int size=Integer.parseInt(element.getAttribute("size"));
        String remark=element.getAttribute("remark");
        if(StringUtils.hasText(color)){
            builder.addPropertyValue("color",color);
        }
        if(StringUtils.hasText(String.valueOf(size))){
            builder.addPropertyValue("size", size);
        }
        if(StringUtils.hasText(remark)){
            builder.addPropertyValue("remark", remark);
        }
    }
}

4.1.4 创建一个 Handler,继承 NamespaceHandlerSupport

定义 Handler 类,继承 NamespaceHandlerSupport ,主要目的是将上面定义的解析器Parser类注册到 Spring 容器中。
package wangjiafu.spring01;

# MyNamespaceHandler.java
public class MyNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        registerBeanDefinitionParser("user",new UserBeanDefinitionParser());

        registerBeanDefinitionParser("phone",new PhoneBeanDefinitionParser());
    }
}
就是将解析器 UserBeanDefinitionParser和 PhoneBeanDefinitionParser 的实例放到全局的Map中,key为user和phone。

4.1.5 编写 Spring.handlersSpring.schemas 文件

编写Spring.handlers和Spring.schemas文件,默认位置放在工程的META-INF文件夹下:
Spring.handlers 文件
http\://www.wangjiafu.com/schema/user=wangjiafu.spring01.MyNamespaceHandler
Spring.schemas 文件
http\://www.wangjiafu.com/schema/user.xsd=META-INF/user.xsd
而 Spring 加载自定义的大致流程是遇到自定义标签然后 就去 Spring.handlers 和 Spring.schemas 中去找对应的 handler 和 XSD , 默认位置是 META-INF ,进而有找到对应的handler以及解析元素的 Parser ,从而完成了整个自定义元素的解析,也就是说 Spring 将向定义标签解析的工作委托给了 用户去实现。

4.1.6 创建测试配置文件

经过上面几个步骤,就可以使用自定义的标签了。在 xml 配置文件中使用如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:myTag="http://www.wangjiafu.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.wangjiafu.com/schema/user http://www.wangjiafu.com/schema/user.xsd">

    <bean id="myTestBean" class="wangjiafu.spring01.MyTestBean"/>

    <myTag:user id="user" email="wangjiafu@163.com" userName="wangjiafu" />
    <myTag:phone id="iphone" color="black" size="128" remark="iphone XR"/>

</beans>
xmlns:myTag表示myTag的命名空间是 http://www.wangjiafu.com/schema/user ,在文章开头的判断处 if (delegate.isDefaultNamespace(ele)) 肯定会返回false,将进入到自定义标签的解析。
测试代码如下:
public class AppTest {
    @Test
    public void MyTestBeanTest() {
        BeanFactory bf = new XmlBeanFactory( new ClassPathResource("spring-config.xml"));
        //MyTestBean myTestBean01 = (MyTestBean) bf.getBean("myTestBean");
        User user = (User) bf.getBean("user");
        Phone iphone = (Phone) bf.getBean("iphone");

        System.out.println(user);
        System.out.println(iphone);
    }
}

4.2 自定义标签的解析

了解了自定义标签的使用后,接下来我们分析下自定义标签的解析,自定义标签解析用的是方法: parseCustomElement(Element ele, @Nullable BeanDefinition containingBd),进入方法体:
# BeanDefinitionParserDelegate
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    // 获取 标签对应的命名空间
    String namespaceUri = getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    }
    // 根据 命名空间找到相应的 NamespaceHandler
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    // 调用自定义的 Handler 处理
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
相信了解了自定义标签的使用方法后,或多或少会对向定义标签的实现过程有一个自己的 想法 其实思路非常的简单:
  • 根据对应的bean 获取对应的命名空间 ;
  • 然后根据命名空间解析对应的处理器;
  • 最后根据用户自定义的处理器进行解析。

4.2.1 获取标签的命名空间

BeanDefinitionParserDelegate.class
标签的解析是从命名空间的提起开始的,元论是区分 Spring中默认标签和自定义标签 还是 区分自定义标签中不同标签的处理器都是以标签所提供的命名空间为基础的,而至于如何提取对应元素的命名空间其实并不需要我们亲内去实现,在 org.w3c.dom.Node 中已经提供了方法供我们直接调用:
String namespaceUri = getNamespaceURI(ele);
@Nullable
public String getNamespaceURI(Node node) {
    return node.getNamespaceURI();
}
这里我们通过DEBUG看出myTag:user自定义标签对应的 namespaceUri 是 http://www.wangjiafu.com/schema/user

4.2.2 提取自定义标签处理器

BeanDefinitionParserDelegate.class
根据 namespaceUri 获取 Handler,这个 映射关系我们在 Spring.handlers 中已经定义了,所以只需要找到该类,然后初始化返回,最后调用该 Handler 对象的 parse() 方法处理,该方法我们也提供了实现。所以上面的核心就在于怎么找到该 Handler 类。调用方法为: this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri)
# DefaultNamespaceHandlerResolver
public NamespaceHandler resolve(String namespaceUri) {
    // 获取所有已经配置的 Handler 映射
    Map<String, Object> handlerMappings = getHandlerMappings();

    // 根据 namespaceUri 获取 handler的信息:这里一般都是类路径
    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 的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);
        }
    }
}
  • 首先调用 getHandlerMappings() 获取所有配置文件中的映射关系 handlerMappings ,就是我们在 Spring.handlers 文件中配置 命名空间与命名空间处理器的映射关系,该关系为 ;
  • 然后根据命名空间 namespaceUri 从映射关系中获取handler的信息:如果为空或者已经初始化了就直接返回;否则根据反射对该handler进行初始化,同时调用其 namespaceHandler.init()方法;
  • 最后将该 Handler 对象缓存
4.2.2.1 getHandlerMappings() 获取所有已经配置的 Handler 映射
读取 Spring.handlers 配置文件并将配置文件缓存在map中
# DefaultNamespaceHandlerResolver
private Map<String, Object> getHandlerMappings() {
   Map<String, Object> handlerMappings = this.handlerMappings;
   if (handlerMappings == null) {
      synchronized (this) {
         handlerMappings = this.handlerMappings;
         if (handlerMappings == null) {
            if (logger.isTraceEnabled()) {
               logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
            }
            try {
               Properties mappings =
                     PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
               if (logger.isTraceEnabled()) {
                  logger.trace("Loaded NamespaceHandler mappings: " + mappings);
               }
               handlerMappings = new ConcurrentHashMap<>(mappings.size());
               CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
               this.handlerMappings = handlerMappings;
            }
            catch (IOException ex) {
               throw new IllegalStateException(
                     "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
            }
         }
      }
   }
   return handlerMappings;
}
由以上源码可知,借助了工具类 PropertiesLoaderUtils 对属性 handlerMappingsLocation 进行了配置文件的读取,handlerMappingsLocation 被默认初始化为 META-INF/Spring.handlers
4.2.2.2 namespaceHandler.init() 调用自定义 NamespaceHandler 的 init() 方法
我们再次回忆下示例中对于命名空间处理器的内容:
public class MyNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("user",new UserBeanDefinitionParser());
        registerBeanDefinitionParser("phone",new PhoneBeanDefinitionParser());
    }
}
当得到自定义命名空间对应的Handler后会马上执行 namespaceHandler.init()进行自定义 BeanDefinitionParser的注册,在这里,你可以注册多个标签解析器,如当前示例中 标签就使用 new UserBeanDefinitionParser()解析器; 就使用new PhoneBeanDefinitionParser()解析器。
上面我们已经说过, init() 中的 registerBeanDefinitionParser() 方法 其实就是 将映射关系放在一个 Map 结构的 parsers 对象中
private final Map parsers

4.2.3 标签解析

BeanDefinitionParserDelegate.class
得到了解析器和分析的元素后,Spring就可以将解析工作委托给自定义解析器去解析了,对于标签的解析使用的是: NamespaceHandler.parse(ele, new ParserContext(this.readerContext, this, containingBd))方法。
进入到方法体内:
public BeanDefinition parse(Element element, ParserContext parserContext) {
    BeanDefinitionParser parser = findParserForElement(element, parserContext);
    return (parser != null ? parser.parse(element, parserContext) : null);
}
调用 findParserForElement() 方法获取 BeanDefinitionParser 实例,其实就是 获取在 init() 方法里面注册的实例对象。如下:
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    //获取元素名称,也就是<myTag:user中的 user
    String localName = parserContext.getDelegate().getLocalName(element);
    //根据 user 找到对应的解析器,也就是在示例registerBeanDefinitionParser("user",new UserBeanDefinitionParser())中注册的解析器
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
                "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}
  • 获取 localName,在上面的例子中就是 user ;
  • 然后从 Map 实例 parsers 中获取 UserBeanDefinitionParser实例对象。
  • 返回 BeanDefinitionParser 对象后,调用其 parse(),该方法在 AbstractBeanDefinitionParser 中实现。
我们可以从DEBUG中看出,当前标签是 UserBeanDefinitionParser,返回的是UserBeanDefinitionParser实例对象。
接下来我们看看 parser.parse (element, parserContext),该方法在 AbstractBeanDefinitionParser 中实现:
# AbstractBeanDefinitionParser
public final BeanDefinition parse(Element element, ParserContext parserContext) {
    //将自定义配置文件解析成AbstractBeanDefinition对象
    AbstractBeanDefinition definition = parseInternal(element, parserContext);
    if (definition != null && !parserContext.isNested()) {
        try {
            String id = resolveId(element, definition, parserContext);
            if (!StringUtils.hasText(id)) {
                parserContext.getReaderContext().error(
                        "Id is required for element '" + parserContext.getDelegate().getLocalName(element)
                                + "' when used as a top-level tag", element);
            }
            String[] aliases = null;
            if (shouldParseNameAsAliases()) {
                String name = element.getAttribute(NAME_ATTRIBUTE);
                if (StringUtils.hasLength(name)) {
                    aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
                }
            }
            //将 AbstractBeanDefinition 转换为 BeanDefinitionHolder 并注册
            BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
            registerBeanDefinition(holder, parserContext.getRegistry());
            if (shouldFireEvents()) {
                BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);
                postProcessComponentDefinition(componentDefinition);
                parserContext.registerComponent(componentDefinition);
            }
        }
        catch (BeanDefinitionStoreException ex) {
            String msg = ex.getMessage();
            parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element);
            return null;
        }
    }
    return definition;
}
虽然说是对自定义配置文件的解析,但是我们可以看到在这个函数中大部分的代码用来处理 将解析后的AbstractBeanDefinition转换为BeanDefinitionHolder并注册的功能,而真正去 做解析的事情委托了给 parseInternal() 方法,真是这句代码 调用了我们的自定义解析函数。在parseInternal() 方法中,并不是直接调用自定义的doParse函数,而是进行了一些列的数据准备,包括对beanClass,scope,lazyInit等属性的准备。
4.2.3.1 parseInternal(element, parserContext) 将自定义配置文件解析成AbstractBeanDefinition对象
我们进入到 AbstractSingleBeanDefinitionParser.parseInternal() 方法中:
# AbstractSingleBeanDefinitionParser
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
    // 创建一个BeanDefinitionBuilder,内部实际上是创建一个GenericBeanDefinition的实例,用于存储自定义标签的元素
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();

    // 获取父类元素
    String parentName = getParentName(element);
    if (parentName != null) {
        builder.getRawBeanDefinition().setParentName(parentName);
    }

    // 获取自定义标签中的 class,这个时候会去调用自定义解析中的 getBeanClass()
    Class<?> beanClass = getBeanClass(element);
    if (beanClass != null) {
        builder.getRawBeanDefinition().setBeanClass(beanClass);
    }
    else {
        // beanClass 为 null,意味着子类并没有重写 getBeanClass() 方法,则尝试去判断是否重写了 getBeanClassName()
        String beanClassName = getBeanClassName(element);
        if (beanClassName != null) {
            builder.getRawBeanDefinition().setBeanClassName(beanClassName);
        }
    }
    builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
    BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
    if (containingBd != null) {
        // Inner bean definition must receive same scope as containing bean.
        builder.setScope(containingBd.getScope());
    }
    if (parserContext.isDefaultLazyInit()) {
        // Default-lazy-init applies to custom bean definitions as well.
        builder.setLazyInit(true);
    }

    // 调用子类的 doParse() 进行解析
    doParse(element, parserContext, builder);
    return builder.getBeanDefinition();
}

public static BeanDefinitionBuilder genericBeanDefinition() {
    return new BeanDefinitionBuilder(new GenericBeanDefinition());
}

protected Class<?> getBeanClass(Element element) {
    return null;
}

protected void doParse(Element element, BeanDefinitionBuilder builder) {
}
在该方法中我们主要关注两个方法: getBeanClass()doParse()对于 getBeanClass() 方法AbstractSingleBeanDefinitionParser 类并没有提供具体实现,而是 直接返回 null,意味着它希望子类能够重写该方法,当然如果 没有重写该方法,则会去调用 getBeanClassName() ,判断子类是否已经重写了该方法。对于 doParse() 则是直接空实现。所以对于 parseInternal() 而言它 总是期待它的子类能够实现 getBeanClass()、doParse(),其中 doParse() 尤为重要,如果你不提供实现,怎么来解析自定义标签呢?
最后再次回顾自定义的解析器:UserBeanDefinitionParser :
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    @Override
    protected Class getBeanClass(Element ele){
        return User.class;
    }
    @Override
    protected void doParse(Element element, BeanDefinitionBuilder builder) {
        String id = element.getAttribute("id");
        String userName=element.getAttribute("userName");
        String email=element.getAttribute("email");
        if(StringUtils.hasText(id)){
            builder.addPropertyValue("id",id);
        }
        if(StringUtils.hasText(userName)){
            builder.addPropertyValue("userName", userName);
        }
        if(StringUtils.hasText(email)){
            builder.addPropertyValue("email", email);
        }
    }
}
我们看看 builder.addPropertyValue ("id",id) ,实际上是将自定义标签中的属性解析,存入 BeanDefinitionBuilder 中的 beanDefinition实例中:
private final AbstractBeanDefinition beanDefinition;

public BeanDefinitionBuilder addPropertyValue(String name, @Nullable Object value) {
    this.beanDefinition.getPropertyValues().add(name, value);
    return this;
}
4.2.3.2 将 AbstractBeanDefinition 转换为 BeanDefinitionHolder 并注册
AbstractBeanDefinitionParser.class
最后 将 AbstractBeanDefinition 转换为 BeanDefinitionHolder 并注册 registerBeanDefinition(holder, parserContext.getRegistry());这就和默认标签的注册是一样了。

4.3 总结

至此,自定义标签的解析过程已经分析完成了。其实整个过程还是较为简单:
  1. 首先会加载 Spring.handlers 文件,将其中内容进行解析,形成 这样的一个映射;
  2. 然后根据获取的 namespaceUri 就可以得到相应的类路径,对其进行初始化得到相应的 Handler 对象,
  3. 调用 handler.parse() 方法,在该方法中根据标签的 localName 得到相应的 BeanDefinitionParser 实例对象,调用 BeanDefinitionParser.parse() 方法,该方法定义在 AbstractBeanDefinitionParser 抽象类中,核心逻辑封装在其 parseInternal() 中,该方法返回一个 AbstractBeanDefinition 实例对象,其主要是在 AbstractSingleBeanDefinitionParser 中实现,对于自定义的 Parser 类,其需要实现 getBeanClass() 或者 getBeanClassName() 和 doParse()
  4. 最后将 AbstractBeanDefinition 转换为 BeanDefinitionHolder 并注册 。

通过对本章的了解,相信读者对Spring中⾃定义标签的使⽤以及在解析⾃定义标签过程中Spring为我们做了哪些⼯作会有⼀个全⾯的了解。到此为⽌我们已经完成了Spring中 全部的解析⼯作,也就是说到现在为⽌我们已经理解了Spring将bean从配置⽂件到加载到内存中的全过程。

⽽接下来的任务便是如何使⽤这些 bean,下⼀章将介绍bean的加载。


http://www.kler.cn/a/555362.html

相关文章:

  • EasyExcel的简单使用
  • 网络缓存加速技术解析:从诞生到演进
  • Spring Cloud LoadBalancer 负载均衡
  • 自然语言处理:第九十二章 chatBI 经验(转载)
  • 【DeepSeek】如何将DeepSeek部署到本地?如何给本地 LLM 提供UI界面?CherryStudio 的使用
  • 对CSS了解哪些?
  • Lab12_ Blind SQL injection with conditional errors
  • MariaDB10创建用户并授权
  • 一个前端,如何同时联调多个后端
  • Qt的QStackedWidget样式设置
  • 数据结构之堆(Heap)
  • 【C#零基础从入门到精通】(二十六)——C#三大特征-多态详解
  • Airtest与持续集成(CI)工具的集成实操案例
  • 【Leetcode 每日一题】2595. 奇偶位数
  • Mac安装配置Tomcat 8
  • Django5 实用指南(四)URL路由与视图函数
  • 【CV前沿】YOLOv12: Attention-Centric Real-Time Object Detectors
  • DAY10 Tensorflow 基本函数使用
  • k8s Container runtime network not ready
  • Ubuntu部署ktransformers