美文网首页
自定义标签解析

自定义标签解析

作者: 北海北_6dc3 | 来源:发表于2020-03-09 11:44 被阅读0次

在很多情况下,我们需要为系统提供可配置化支持,简单的做法可以直接基于Spring的标准 bean 来配置,但配置较为复杂或者需要更多丰富控制的时候,会显得非常笨拙。一般的做法会用原生态的方式去解析定义好的XML文件,然后转化为配置对象。这种方式当然可以解决所有问题,但实现起来比较繁琐,特别是在配置非常复杂的时候,解析工作是一个不得不考虑的负担。Spring提供了可扩展Schema的支持,这是一个不错的折中方案,扩展Spring自定义标签配置大致需要以下几个步骤(前提是要把Spring的Core包加入项目中)。

  • 创建一个需要扩展的组件。
  • 定义一个XSD文件描述组件内容。
  • 创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义。
  • 创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器。
  • 编写Spring.handlers和Spring.schemas文件。

现在我们就按照上面的步骤带领读者一步步地体验自定义标签的过程。

简单例子

  • 定义一个对象User ,带解析
public class User {
 private String userName;
 private String email;
 ...
}
  • 对象解析器UserBeanDefinitionParser
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    //Element对应的类
    protected Class getBeanClass(Element element) {
        return User.class;
    }

    //从element中解析并提取对应的元素
    protected void doParse(Element element, BeanDefinitionBuilder bean) {
        String userName = element.getAttribute("userName");
        String email = element.getAttribute("email");
        //将提取的数据放入到BeanDefinitionBuilder中,待到完成所有bean的解析后统一注册到beanFactory中
        if (StringUtils.hasText(userName)) {
            bean.addPropertyValue("userName", userName);
        }
        if (StringUtils.hasText(email)) {
            bean.addPropertyValue("email", email);
        }
    }
}
  • 注册解析器MyNamespaceHandler
public class MyNamespaceHandler extends NamespaceHandlerSupport {
    public void init() {
        registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
    }
}
  • xsd定义,user.xsd
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
        targetNamespace="http://www.lexueba.com/schema/user"
        xmlns:tns="http://www.lexueba.com/schema/user"
        elementFormDefault="qualified">
    <element name="user">
        <complexType>
         <attribute name="id" type="string"/>
         <attribute name="userName" type="string"/>
         <attribute name="email" type="string"/>
        </complexType>
    </element>
</schema>
  • spring.schemas定义
http\://www.lexueba.com/schema/user.xsd=META-INF/user.xsd
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:myname="http://www.lexueba.com/schema/user"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.lexueba.com/schema/user
       http://www.lexueba.com/schema/user.xsd">
    <myname:user id="testbean" userName="aaa" email="bbb"/>
</beans>
  • 调用
    @Test
    public void customerUserLoad() throws IOException {
        ApplicationContext bf = new ClassPathXmlApplicationContext ("user.xml");
        User user=(User) bf.getBean("testbean");
        System.out.println(user.getUserName()+","+user.getEmail());
    }
  • 运行结果
aaa,bbb

源码分析

我们上面定义了很多东西,目标就是能实现以下定义,来初始化User对象

<myname:user id="testbean" userName="aaa" email="bbb"/>

我们首先分析,根据命名空间,获取handler

    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        String namespaceUri = getNamespaceURI(ele);
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }

public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
    @Override
    public NamespaceHandler resolve(String namespaceUri) {
        //通过 "META-INF/spring.handlers"定义
        Map<String, Object> handlerMappings = getHandlerMappings();
        //获取相应handler ClassName
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if (handlerOrClassName == null) {
            ....
        }
        else if (handlerOrClassName instanceof NamespaceHandler) {
            ....
        }
        else {
            String className = (String) handlerOrClassName;
            try {
                Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
                //实例化,并调用init方法,
                NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
                //init方法,主要注册我们的parser,下一步使用
                namespaceHandler.init();
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
            catch (ClassNotFoundException ex) {
                throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
                        namespaceUri + "] not found", ex);
            }
            catch (LinkageError err) {
                throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
                        namespaceUri + "]: problem with handler class file or dependent class", err);
            }
        }
    }
}

根据handler获取parse解析器

public abstract class NamespaceHandlerSupport implements NamespaceHandler {
    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        return findParserForElement(element, parserContext).parse(element, parserContext);
    }

    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        String localName = parserContext.getDelegate().getLocalName(element);
        //这里的parsers在前面init方法注册了。获得解析器。
        BeanDefinitionParser parser = this.parsers.get(localName);
        if (parser == null) {
            parserContext.getReaderContext().fatal(
                    "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
        }
        return parser;
    }
}

根据解析器调用parse,并后置处理

public abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser {
    @Override
    public final BeanDefinition parse(Element element, ParserContext parserContext) {
        AbstractBeanDefinition definition = parseInternal(element, parserContext);
            ...
        return definition;
    }


}

public abstract class AbstractSingleBeanDefinitionParser extends AbstractBeanDefinitionParser {
    @Override
    protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        ....
        //调用定义的doParse方法。
        doParse(element, parserContext, builder);
        return builder.getBeanDefinition();
    }

    protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
        doParse(element, builder);
    }
}

这里parseInternal,会调用AbstractSingleBeanDefinitionParser 类的parseInternal是因为我们定义的解析器继承的这个类AbstractSingleBeanDefinitionParser

public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser

相关文章

  • Spring4 自定义标签解析源码

    介绍 Spring将标签解析分为自定义标签解析和默认标签解析。 自定义标签解析开始位置 BeanDefinitio...

  • 1-3自定义标签解析-Spring源码分析

    看文章之前 首先问自己几个问题: 1自定义标签解析是如何实现bean的解析和注册的 2自定义标签解析和默认标签解析...

  • Spring源码分析-bean的解析(3)

    Spring源码分析-bean的解析(3) 当前版本 Spring 4.3.8 自定义标签的解析 自定义标签使用 ...

  • 3.自定义标签解析流程

    解析总流程 自定义标签解析入口 让我们先看BeanDefinitionParserDelegate类的parseC...

  • 简书文章爬虫解析

    核心解析 解析过滤简书自定义标签,文章再用其他富文本逆向解析即可 用vue quill editor解析 完整代码

  • Spring xml默认标签的解析

    默认标签的解析 之前提高过Spring中的标签包括默认标签和自定义标签两种,而两种标签的用法以及解析方式存在着很大...

  • spring解析自定义标签

    title: spring解析自定义标签tags: spring,源码grammar_cjkRuby: true ...

  • Freemarker学习笔记

    模板基础 插值 ${....} :解析变量输出真实的值 FTL标签 以#开头,用户自定义标签以 @开头 注释<#!...

  • dubbo服务导出

    dubbo版本:2.7.2 一、spring解析自定义扩展标签。 spring分析xml节点的可以看出对于自定义命...

  • 第七章 AOP

    整体流程:1、解析XML配置:编写一个自定义标签解析器Spring容器,加载这个解析器,并parse配置解析器自动...

网友评论

      本文标题:自定义标签解析

      本文链接:https://www.haomeiwen.com/subject/lgljdhtx.html