缘起 Dubbo ,讲讲 Spring XML Schema 扩展机制
背景
在 Dubbo 中,可以使用 XML 配置相关信息,也可以用来引入服务或者导出服务。配置完成,启动工程,Spring 会读取配置文件,生成注入 相关 Bean。那 Dubbo 如何实现自定义 XML 被 Spring 载入读取?
Spring XML Schema 扩展机制。从 Spring 2.0 开始,Spring 开始提供了一种基于 XML Schema 格式扩展机制,用于定义和配置 bean。
Spring XML Schema 扩展机制
实现 Spring XML Schema 扩展,其实非常简单,只需要完成下面四步。
- 创建 XML Schema 文件,由于该文件后缀名为 xsd,下面称为 XSD 文件。
- 编写实现一个或多个
BeanDefinitionParser
。 - 编写
NamespaceHandler
实现类。 - 注册
NamespaceHandler
以及 XSD 文件。
我们按照以上步骤,最终完整 Spring 解析如下配置。
<?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:demo="http://www.test.com/demo"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.test.com/demo http://www.test.com/demo/demo.xsd">
<demo:application name="test" id="test"/>
</beans>
创建 XSD 文件
XSD 文件,主要用来定义 XML 格式,用来验证 XML 合法性。在 IDE 中,导入 XSD 文件,编辑 XML 文件可以获得相关提示。
下面我们生成一个 XSD 文件。
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.test.com/demo"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.test.com/demo"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:element name="application">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
上面 XSD 文件中 http://www.test.com/demo 为自定义命名空间地址,下面将会使用到。
实现 BeanDefinitionParser
这里实现 BeanDefinitionParser,真正解析 XML 动作在这里完成。
由于上面的例子比较简单,我们可以直接继承 Spring 提供的抽象类 AbstractSingleBeanDefinitionParser
,然后实现相关方法就可以了。
public class DemoBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
/**
* 返回最会需要注入 Spring Bean 的类型
* @param element
* @return
*/
@Override
protected Class<?> getBeanClass(Element element) {
return DemoApplication.class;
}
/***
* 这个方法完成真正解析动作
* @param element
* @param builder
*/
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String name=element.getAttribute("name");
builder.addPropertyValue("name",name);
}
}
当然也可以直接实现 BeanDefinitionParser,这样更加灵活,但是这样相比于上面这个就比较复杂了。
public class BeanApplicationDefinitionParser implements BeanDefinitionParser {
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
String name=element.getAttribute("name");
// Bean 定义,最后根据这个生产 Bean
RootBeanDefinition rootBeanDefinition=new RootBeanDefinition();
rootBeanDefinition.setBeanClass(DemoApplication.class);
rootBeanDefinition.setLazyInit(false);
// 添加解析的属性
rootBeanDefinition.getPropertyValues().add("name",name);
// 将生成的 BeanDefinition 注册,少了这一步将会导致最后生成 Bean 时报错
parserContext.getRegistry().registerBeanDefinition("application",rootBeanDefinition);
return rootBeanDefinition;
}
}
实现 NamespaceHandler
这一步实现 NamespaceHandler,开发者自定义 NamespaceHandler 只要继承 NamespaceHandlerSupport
抽象类,实现 init
方法。在这个方法中注册上面一步实现 BeanDefinitionParser
。
public class DemoNameSpaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// elementName 为命名空间
registerBeanDefinitionParser("application",new BeanApplicationDefinitionParser());
}
}
注册 XSD 以及 NamespaceHandler
这一步我们需要在 META-INF 中生成两个配置文件,分别为 spring.handlers
,spring.schemas
。
spring.schemas
指定 XSD 文件路径。
http://www.test.com/demo/demo.xsd=com/spring/learning/xml/schemas/autoring/leanrn/demo.xsd
spring.handlers
指定 NamespaceHandler
完整类名,既包含前面的包名。
这里需要注意的是
:
需要进行转义
测试运行
首先我们生产 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:demo="http://www.test.com/demo"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.test.com/demo http://www.test.com/demo/demo.xsd">
<demo:application name="test" id="test"/>
</beans>
这里需要注意需要使用 XSD 文件中定义 http://www.test.com/demo
。
接著我们使用 SpringBoot ,导入 XML 文件,然后运行。
@SpringBootApplication
@ImportResource(locations = {"classpath:applicationContext.xml"})
public class XmlSchemaApplication {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(XmlSchemaApplication.class, args);
DemoApplication demoApplication=applicationContext.getBean(DemoApplication.class);
System.out.println("application name is "+demoApplication.getName());
}
}
输出结果为: