前言
在當前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等問題。