3.1Spring源码解析——自定义标签的使用

 当需要为系统提供可配置化支持的时候。一般的做法会使用原生态的方式去解析定义好的XML文件,然后转化为配置对象。但是这种方法比较繁琐。Spring提供了可扩展的Schema的支持,这是一个不错的这种方案。自定义扩展的标签主要有一下几步:

  • 1.创建一个需要扩展的组件
  • 2.定义一个XSD文件描述组件内容
  • 3.创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义
  • 4.创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组建注册到Spring容器
  • 5.编写Spring.handlers和Spring.schemas文件
    <meta charset="utf-8">

实际操作如下:

目录结构为


目录结构.png

(1)创建一个普通的POJO

创建一个普通的POJO.png

(2)定义一个XSD文件描述组件内容

定义一个XSD文件描述组件内容.png

在XSD文件中描述了一个新的targetNamespace,并在这个空间中定义了一个name为user的element,user有3个属性,id,userName和email且都是string类型。这三个类主要用于验证spring配置文件中自定义格式。

(3)创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义

创建一个文件,实现BeanDefinitionParser接口.png

(4)创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注入到Spring容器

创建一个Handler文件,扩展自NamespaceHandlerSupport.png

(5)编写Spring.handlers和Spring.schemas文件。默认位置是在工程的/META-INF/文件下,也可以通过Spring的扩展或者修改源码的方式改变路径

Spring.handlers内容如下

http\://www.acy.com/acy/schema/user=MyNameSpaceHandler

Spring.schemas文件内容如下

http\://www.acy.com/acy/schema/user.xsd=META-INF/user.xsd

到这里,自定义的配置就结束了,而Spring加载自定义的大致流程就是遇到自定义标签然后就去Spring.handlers和Spring.schemas文件中去找对应的handler和XSD,进而找到嘴硬的handler以及解析元素的Parser,从而完成整个自定义元素的解析。

(6)创建测试配置文件,在配置文件中,引入对应的命名空间以及XSD文件,然后就可以使用自定义标签了

创建测试配置文件.png

这里的test:user需要这么写的原因是因为xmlns:test指向的是"http://www.acy.com/acy/schema/user",这个指向的是NamespaceHandler的映射,如果写的是xmlns:myBean那么就是<myBean:user 同理也可以注册多个标签

(7)测试

测试.png
结果.png

 很多支持spring的框架,都是用这种方式对spring进行适配的。我们用dubbo这个框架来举例,自定义标签的使用。


dubbo结构.png

 在dubbo-config模块里面有一个dubbo-config-spring的字模块,在这个里面就是利用了spring的扩展的Schema的支持。
dubbo-config模块.jpg

 下面按照上面步骤进行解析扩展:
(1)创建一个需要扩展的组件;

&esmp;个人认为这个扩展的组建就是AbstractConfig类,这个类在dubbp-config-api模块中,这个一个基础的抽象类,这个类有很多个子类,这里展示部分子类


部分子类.jpg

 再展示部分AbstractMethodConfig 的配置项,用过dubbo的应该熟悉这些属性
public abstract class AbstractMethodConfig extends AbstractConfig {

    private static final long serialVersionUID = 1L;

    // timeout for remote invocation in milliseconds
    protected Integer timeout;

    // retry times
    protected Integer retries;

    // max concurrent invocations
    protected Integer actives;

    // load balance
    protected String loadbalance;

    // whether to async
    protected Boolean async;

    // whether to ack async-sent
    protected Boolean sent;

    // the name of mock class which gets called when a service fails to execute
    protected String mock;

    // merger
    protected String merger;

    // cache
    protected String cache;

    // validation
    protected String validation;

    // customized parameters
    protected Map<String, String> parameters;
    .....
}

(2)定义一个XSD文件描述组件内容
 这个文件就是dubbo.xsd文件,这个里面定义了标签的使用规范,列举部分内容,这个文件如果知道xsd对应的写法会清楚点

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:beans="http://www.springframework.org/schema/beans"
            xmlns:tool="http://www.springframework.org/schema/tool"
            xmlns="http://dubbo.apache.org/schema/dubbo"
            targetNamespace="http://dubbo.apache.org/schema/dubbo">

    <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
    <xsd:import namespace="http://www.springframework.org/schema/beans"/>
    <xsd:import namespace="http://www.springframework.org/schema/tool"/>

    <xsd:annotation>
        <xsd:documentation>
            <![CDATA[ Namespace support for the dubbo services provided by dubbo framework. ]]></xsd:documentation>
    </xsd:annotation>

    <xsd:complexType name="abstractMethodType">
        <xsd:attribute name="timeout" type="xsd:string" default="0">
            <xsd:annotation>
                <xsd:documentation><![CDATA[ The method invoke timeout. ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <xsd:attribute name="retries" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation><![CDATA[ The method retry times. ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <xsd:attribute name="actives" type="xsd:string">
            <xsd:annotation>
                <xsd:documentation><![CDATA[ The max active requests. ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
        <xsd:attribute name="connections" type="xsd:string">
            <xsd:annotation>
           <xsd:documentation>
                    <![CDATA[ The exclusive connections. default share one connection. ]]></xsd:documentation>
            </xsd:annotation>
        </xsd:attribute>
      .....

(3)创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义
 这里的实现BeanDefinitionParser接口的是DubboBeanDefinitionParser类,里面对不同的config进行了不同的处理逻辑,主要是按照对应的解析的bean的类型,来判断处理的逻辑,大家可以看看

public class DubboBeanDefinitionParser implements BeanDefinitionParser {

    private static final Logger logger = LoggerFactory.getLogger(DubboBeanDefinitionParser.class);
    private static final Pattern GROUP_AND_VERION = Pattern.compile("^[\\-.0-9_a-zA-Z]+(\\:[\\-.0-9_a-zA-Z]+)?$");
    private final Class<?> beanClass;
    private final boolean required;

    public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
        this.beanClass = beanClass;
        this.required = required;
    }

    @SuppressWarnings("unchecked")
    private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
        //这是需要注册Class到beanDefinition包含的BeanClass属性中
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanClass);
        beanDefinition.setLazyInit(false);
        //element中包含的是application标签中的信息
        String id = element.getAttribute("id");
        if ((id == null || id.length() == 0) && required) {
            String generatedBeanName = element.getAttribute("name");
            if (generatedBeanName == null || generatedBeanName.length() == 0) {
                if (ProtocolConfig.class.equals(beanClass)) {
                    generatedBeanName = "dubbo";
                } else {
                    generatedBeanName = element.getAttribute("interface");
                }
            }
            if (generatedBeanName == null || generatedBeanName.length() == 0) {
                generatedBeanName = beanClass.getName();
    ....

(4)创建一个Handler类,扩展自NamespaceHandlerSupport,目的是将组件注入到Spring容器
这里的Handler类就是DubboNamespaceHandler类

public class DubboNamespaceHandler extends NamespaceHandlerSupport {

    static {
        Version.checkDuplicate(DubboNamespaceHandler.class);
    }

    @Override
    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
        registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
        registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
        registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
        registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
        registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
        registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }

}

(5)编写Spring.handlers和Spring.schemas文件。默认位置是在工程的/META-INF/文件下,也可以通过Spring的扩展或者修改源码的方式改变路径
&esmp;这里的handlers文件就是spring.handlers文件。里面内容比较简单,定义了对应的解析类的位置

http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容