前言
在当前 Java 生态,Spring 算的上是最核心的框架,所有的开发组件想要得到大范围更便捷的使用,都要和 Spring 进行整合,比如我们熟知的 Mybatis、Dubbo 等,以及内部封装的各类组件包括 Redis、MQ、配置中心等。
有了整合这一步,我们只需引入相应的 jar,比如 mybatis-spring,然后进行简单的配置后即可在 Spring 工程中使用 Mybatis 的功能,也正是由于这样的便捷性,导致很多时候我们没有对其进行深究。
所谓整合,即在 Spring 的框架下进行扩展,让框架能无缝的与 Spring 工程配合使用。Spring 设计了良好的扩展的机制,本文将对 Spring 的扩展方法及原理进行简单介绍。
XML Schema 扩展
打开mybatis-spring
、dubbo
的源码会发现在META-INF
目录下有两个文件(如下图所示),spring.handlers
与spring.schemas
,这两个文件就是 XML Schema 扩展的关键入口点。
XSD
XSD,XML Schema Definition,XML 定义。
XML Schema 定义 XML 文档的结构,XML Schema 语言也称为 XML 定义,即 XSD。
XSD 的语法及使用比较简单,可参考https://www.w3school.com.cn/schema/index.asp进行了解熟悉。
简单的说,XSD 用于制定 xml 文件规范,包括 xml 中的元素(简单元素、复杂元素)、属性、以及属性类型及约束等。
Spring XML Schema 扩展的第一步就是要定义一个 xsd 文件,比如 spring-beans 对应 xsd 文件为http://www.springframework.org/schema/beans/spring-beans.xsd,如下图:
为了简单介绍 Spring XML Schema 扩展实现,下面将一个简单例子(模拟一个简单的分布式 id 生成器,不会实现具体功能)进行说明,xsd 定义如下(文件命名为 DistributedId.xsd,在 META-INF 目录下):
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.hexup.com/schema/distributed-id"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.hexup.com/schema/distributed-id">
<xsd:element name="distributed-id">
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string"></xsd:attribute>
<xsd:attribute name="bizCode" type="xsd:string"></xsd:attribute>
<xsd:attribute name="length" type="xsd:int"></xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>
复制代码
上述 xsd 文件里定义了一个复杂元素 distributed-id,包含属性 id,bizCode,length,形如:
<distributed-id id="xxx" bizCode="xxx" length="xxx"></distributed-id>
复制代码
注意:xmlns,即为 xml namespace,xml 命名空间,后面跟的 http 链接地址可以不存在,因为 xsd 会放在当前工程的 META-INF 下。
配置 spring.handlers 和 spring.schemas
如下两张图所示,spring.schemas
文件中用于说明 xsd 的文件路径,spring.schemas
文件用于说明解析此类 xsd 定义的标签的处理类,下面会对处理类进行详细说明。
NameSpaceHandler 与 BeanDefinitionParser
定义类 DistributedIdNamespaceHandler 继承 NamespaceHandlerSupport,init 方法用于注册 BeanDefinition 解析器,也就是解析 xml 中对应标签为 Spring Bean。
public class DistributedIdNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("distributed-id", new DistributedIdParser());
}
}
复制代码
同时要创建 BeanDefinitionParser
public class DistributedIdParser implements BeanDefinitionParser {
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 解析xml内的标签
String bizCode = element.getAttribute("bizCode");
int length = Integer.valueOf(element.getAttribute("length"));
String id = element.getAttribute("id");
// 创建DistributedIdFactoryBean bean
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
builder.getRawBeanDefinition().setBeanClass(DistributedIdFactoryBean.class);
builder.setScope(BeanDefinition.SCOPE_SINGLETON);
builder.addPropertyValue("bizCode", bizCode);
builder.addPropertyValue("length", length);
BeanDefinition beanDefinition = builder.getBeanDefinition();
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
return beanDefinition;
}
}
复制代码
其中 DistributedIdFactoryBean 实现 FactoryBean 接口用于创建 DistributedIdComponent Bean,如下
public class DistributedIdFactoryBean implements InitializingBean, FactoryBean<DistributedIdComponent> {
private String bizCode;
private int length;
private DistributedIdComponent distributedIdComponent;
@Override
public DistributedIdComponent getObject() throws Exception {
return distributedIdComponent;
}
@Override
public Class<?> getObjectType() {
return DistributedIdComponent.class;
}
@Override
public boolean isSingleton() {
return false;
}
@Override
public void afterPropertiesSet() throws Exception {
distributedIdComponent = new DistributedIdComponent(bizCode, length);
}
public void setBizCode(String bizCode) {
this.bizCode = bizCode;
}
public void setLength(int length) {
this.length = length;
}
}
复制代码
目标 Bean DistributedIdComponent 如下:
public class DistributedIdComponent {
private String bizCode;
private int length;
public DistributedIdComponent() {
}
public DistributedIdComponent(String bizCode, int length) {
this.bizCode = bizCode;
this.length = length;
}
public String generateId() {
System.out.println("mock generate id");
return String.valueOf(System.currentTimeMillis()).substring(0, length);
}
public String getBizCode() {
return bizCode;
}
public void setBizCode(String bizCode) {
this.bizCode = bizCode;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
}
复制代码
使用
spring 配置文件,spring-service.xml
中配置 distributed-id 标签以及对应的属性值,如下:
<?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:distributed-id="http://www.hexup.com/schema/distributed-id"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.hexup.com/schema/distributed-id http://www.hexup.com/schema/distributed-id.xsd">
<distributed-id:distributed-id id="test" bizCode="test" length="8"></distributed-id:distributed-id>
</beans>
复制代码
运行容器验证:
public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-service.xml");
DistributedIdComponent bean = context.getBean(DistributedIdComponent.class);
String id = bean.generateId();
System.out.println("id:" + id);
}
}
复制代码
总结
本文主要介绍了 Spring XML Schema 扩展机制的使用方法,大致步骤为定义 XSD 文件、配置 spring.schemas、编码实现 NameSpaceHanlder 和 BeanDefinitionParser 实现类、配置 spring.handlers。但未说明具体的实现原理,后续会有一篇文章详细介绍 Spring 源码是怎么实现扩展的,以及介绍为什么使用 FactoryBean 来创建具体的 Bean 等问题。
评论