在Spring XML配置文件中除了Spring 默认的Namespace,今天我们来看一下如何自定义Namespace

1. Spring自定义XML的Namespace原理

整个Spring容器启动的时候流程还是一样,但是在加载Bean的定义的时候,XML配置文件调用的是 AbstractXmlApplicationContext#loadBeanDefinitions 方法来加载XML中的Bean的定义。然后通过 XmlBeanDefinitionReader 从设置的默认位置或者指定位置的xml解析成为Document到内存。BeanDefinitionDocumentReader 负责解析 XML Document中每个 Element。

整个过程会读取META-INF/spring.schemas文件中配置的Namespace和XSD文件的对应关系进行校验

在解析的过程中会去判断是Spring默认的Namespace还是用户自定义Namespace

  • Spring 默认Element处理

    默认的Element: import,alias,bean,beans,这些都是由 DefaultBeanDefinitionDocumentReader 提供默认解析

  • 自定义的Element处理

    通过获取配置在 META-INF/spring.handlers 文件中对应Namespace的处理类。这个Namespace的处理类实现 NamespaceHandler或者NamespaceHandlerSupport

然后调用NamespaceHandler具体实例的NamespaceHandler#parse方法对Element进行解析。

2. Spring自定义XML的Namespace实战

定义一个和Spring默认的bean拥有相同功能的Element,这个Element可以叫:mxsmBean, 具体的步骤如下图:

2.1 XSD定义

通过XSD文件定义mxsmBean需要有哪些属性。

<?xml version="1.0" encoding="UTF-16" ?>
<xsd:schema xmlns="https://github.com/mxsm/schema/mxsm"  (1)
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"  
            targetNamespace="https://github.com/mxsm/schema/mxsm">  (2)

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

    <xsd:element name="mxsmBean">
        <xsd:complexType>
            <xsd:attribute name="name" type="xsd:string" use="required"/>
            <xsd:attribute name="class" type="xsd:string" use="required"/>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>
复制代码

(1)和(2)是加自己的Namescape,XSD存放的位置如下:

2.2 编写NamespaceHandler接口实现

这个接口主要是用来处理我们对应的Element。例如在 github.com/mxsm/schema… 下面我只是定义application这个Element所以就只需要解析这一个就可以了。

NamespaceHandlerSupport是实现了部分NamespaceHandler接口的抽象方法,一般情况下我们实现NamespaceHandlerSupport类

public class MxsmSchemaHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("mxsmBean", new MxsmBeanDefinitionParser());
        //如果有多个Element就调用多个
    }
}

public class MxsmBeanDefinitionParser implements BeanDefinitionParser {

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        String aClass = element.getAttribute("class");
        System.out.println(aClass);
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(aClass);
        AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        parserContext.getRegistry().registerBeanDefinition(element.getAttribute("name"), beanDefinition);
        return beanDefinition;
    }
}
复制代码

每一个对应的标签对应一个解析类,解析类实现 BeanDefinitionParser 接口。

2.3 配置META-INF/spring.schemas和META-INF/spring.handlers文件配置

spring.schemas作用:配置自定义Namespace的xsd文件的存放位置

https\://github.com/mxsm/schema/mxsm/mxsm.xsd=com/github/mxsm/xml/xsd/mxsm.xsd
复制代码

spring.handlers作用:配置自定义Namespace对应的NamespaceHandler

https\://github.com/mxsm/schema/mxsm=com.github.mxsm.handler.MxsmSchemaHandler
复制代码

在项目中存放的位置如图:

2.4 Spring application.xml文件中引入自定义的Element

创建Spring应用的xml文件。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mxsm="https://github.com/mxsm/schema/mxsm"  (1)
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        https://github.com/mxsm/schema/mxsm  (2)
        https://github.com/mxsm/schema/mxsm/mxsm.xsd  (3)
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

        <mxsm:mxsmBean class="com.github.mxsm.bean.MxsmBeanTest" name="test"/>

</beans>
复制代码

(1)、(2),(3) 这三处在使用的时候需要加上。到这里为止,自定义的步骤都已经完成了。接下来就是进行验证我们能不能把这个类注入到Spring 容器中。这里我们写一段测试代码来检验:

public class App {
    public static void main( String[] args ) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        //System.out.println(context.getBean("aaaa",String.class));
        MxsmBeanTest test = context.getBean("test", MxsmBeanTest.class);
        System.out.println(test);
        MxsmBeanTest bean = context.getBean(MxsmBeanTest.class);
        System.out.println(bean);
        System.out.println(test==bean);
    }
}
复制代码

然后运行看一下结果:

从运行结果来看能够获取到 MxsmBeanTest 对应的实例,并且获取的还是单例。那么说明在我们创建默认的时候注册到Spring容器中的定义获取的类实例默认是已单例的形式。

完整代码地址:github.com/mxsm/spring…

3. 总结

  • 在自定义拓展前,首先你要知道你拓展的Element是干什么用的,然后在根据用途来进行解析。比如我上面这个mxsmBean就是为了实现一个简化版本的和Spring默认的Bean类似的功能。
  • Namespace的XSD文件的编写,需要了解XSD的编写(教程参考:www.w3school.com.cn/schema/sche…)
  • 在接口层面需要实现NamespaceHandler以及BeanDefinitionParser接口,两者搭配以前使用。
  • 在文件META-INF/spring.schemas和META-INF/spring.handlers中添加对应配置,Spring会默认加载这两个文件中的配置进行解析。
  • 在Spring的xml配置文件中导入对应自定义的Namespace。

上面就是整个自定义命名空间的过程,步骤以及Spring怎么样去解析。如果想了解更多的细节可以去Spring的网站(docs.spring.io/spring-fram… util(spring-util.xsd)。可以通过这些来学习。这样能够更好的明白和理解自定义的这个过程。


原文链接:https://juejin.cn/post/7054932636048818183