简单说明

Spring自定义标签是一个很常见的功能,常见的aop、mvc、tx等标签就是使用自定义标签的形式扩展的。 所以,如果我们需要自己实现一个类似于mvc驱动标签的功能的时候,封装为这种形式可以通过一行xml配置就开始使用,会变得非常方便。

开始

1、准备配置

首先新建文件 src/main/resources/META-INF/spring.handlers 和 src/main/resources/META-INF/spring.schemas
这两个文件内容如下

# spring.handlers
# 这个文件里的key就是spring的xml配置里的 xmlns:sample="http://www.dongpo.li/schema/sample"
http\://www.dongpo.li/schema/sample=li.dongpo.tc.service.SampleNamespaceHandler
# spring.schemas
# 这个k文件的key就是 xsi:schemaLocation 中的一部分
http\://www.dongpo.li/schema/sample-1.0.0.xsd=/META-INF/sample-1.0.0.xsd
http\://www.dongpo.li/schema/sample.xsd=/META-INF/sample-1.0.0.xsd

2、准备xsd文件

新建文件 src/main/resources/META-INF/sample-1.0.0.xsd

文件内容如下

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.dongpo.li/schema/sample"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans"
            targetNamespace="http://www.dongpo.li/schema/sample"
            elementFormDefault="qualified" attributeFormDefault="unqualified">
    <xsd:import namespace="http://www.springframework.org/schema/beans"/>
    <!-- 这个name指定的sample就是<sample:sample />  配置中的冒号后边的这个sample -->
    <xsd:element name="sample">
        <xsd:annotation>
            <xsd:documentation>自定义标签扩展的示例</xsd:documentation>
        </xsd:annotation>
        <xsd:complexType>
            <xsd:complexContent>
                <!-- 继承定义 从namespace="http://www.springframework.org/schema/beans" -->
                <xsd:extension base="beans:identifiedType">
                    <!-- 定义了两个属性  name和value,name必填 -->
                    <xsd:attribute name="name" type="xsd:string" use="required" />
                    <xsd:attribute name="value" type="xsd:string" use="optional" default="value" />
                </xsd:extension>
            </xsd:complexContent>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

3、配置

spring的xml文件配置示例如下

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:sample="http://www.dongpo.li/schema/sample"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.dongpo.li/schema/sample http://www.dongpo.li/schema/sample.xsd">
    
    <sample:sample name="name" />

    <bean id="sampleService" class="li.dongpo.tc.service.SampleServiceImpl" />
</beans>

其中重要的几个是:
1、xmlns:sample=“http://www.dongpo.li/schema/sample"
2、http://www.dongpo.li/schema/sample http://www.dongpo.li/schema/sample.xsd
3、<sample:sample name=“name” />

sampleService不是我们这次相关的配置,相关类请自行创建,主要是用来测试的和debug,可以按照自己的方式修改或删除。

4、主逻辑

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

public class SampleNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("sample", new BeanDefinitionParser() {
            @Override
            public BeanDefinition parse(Element element, ParserContext parserContext) {
                String name = element.getAttribute("name");
                String value = element.getAttribute("value");
                System.out.println("BeanDefinitionParser start, name=" + name + ", value=" + value);
                return null;
            }
        });
    }

}

这样,通过我们刚新建的spring.handlers文件中的内容就能找到SampleNamespaceHandler这个类。使用的是spring自己实现的SPI机制,感兴趣的可以自行了解。

5、测试代码

public class Application {
    public static void main(String[] args) throws InterruptedException {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        System.out.println("context初始化完成,开始获取bean");
    }
}

执行这个代码就可以看到输出

BeanDefinitionParser start, name=name, value=value

教程就算结束了

使用场景

说这么热闹,这个功能到底有什么用
1、这个功能本身返回的就是BeanDefinition,所以是可以修改bean本身初始化方式的,参见aop的相关部分
2、可以在SampleNamespaceHandler开一个TCP端口做RPC调用,可以参见dubbo的相关实现
3、可以注入一个bean,其中建立TCP连接做消息监听。监听到特定消息反射执行对应的方法,可以实现诸如 mq、配置热加载等的功能

代码

我把代码上传到了github上,感兴趣的同学可以自行下载参照。 https://github.com/remainer-com/spring-sample 参见 spring-ns-sample 模块

后记

1、整体来说还是比较简单的,只是一个简单的Demo。
2、spring.schemas中还可以指定 http://www.dongpo.li/schema/sample.xsd=com/xxx/…/sample-1.0.0.xsd 将xsd文件放在和类同名的包里,具体可以参照aop的实现。