某些场景下我们需要扩展spring标签,让spring可以识别我们自定义的标签,实现自定义bean;比如dubbo中定义dubbo:service
等,shardingsphere在适配spring,在spring配置文件中定义分库分表策略等,今天主要分析如何扩展spring自定义标签以及实现demo。为后边自研分库分表中间件实现spring配置做铺垫。
首先需要简单了解以下信息
XML Schema 是基于 XML 的 DTD 替代者。
XML Schema 描述 XML 文档的结构。
XML Schema 语言也称作 XML Schema 定义(XML Schema Definition,XSD)。
XML Schema 比 DTD 更强大。
元素是每一个 XML Schema 的根元素:
<xs:schema>
...
...
xs:schema>
元素可包含属性。一个 schema 声明往往看上去类似这样:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.w3school.com.cn"
xmlns="http://www.w3school.com.cn"
elementFormDefault="qualified">
...
...
xs:schema>
代码解释:
下面的片断:
xmlns:xs="http://www.w3.org/2001/XMLSchema"
显示 schema 中用到的元素和数据类型来自命名空间 “http://www.w3.org/2001/XMLSchema”。同时它还规定了来自命名空间 “http://www.w3.org/2001/XMLSchema” 的元素和数据类型应该使用前缀 xs:
这个片断:
targetNamespace="http://www.w3school.com.cn"
显示被此 schema 定义的元素 (note, to, from, heading, body) 来自命名空间: “http://www.w3school.com.cn”。
这个片断:
xmlns="http://www.w3school.com.cn"
指出默认的命名空间是 “http://www.w3school.com.cn”。
这个片断:
elementFormDefault="qualified"
指出任何 XML 实例文档所使用的且在此 schema 中声明过的元素必须被命名空间限定。
控制反转或者依赖倒置,对象之间有引用或者依赖关系,由spring 容器来完成,IOC容器实现了对bean管理,所以就涉及到了spring是如何加载bean?以及将bean注册到容器中?
spring对bean的定义接口
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement{}
每一个bean会构建一个BeanDefinition,构建完成后注册到容器中,实际就是spring维护的HashMap中,主要介绍如何加载BeanDefinition
FileSystemXmlApplicationContext初始化入口
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
refresh方法启动容器,包含加载bean,即loadBeanDefinitions
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
没有在loadBeanDefinitions方法直接解析xml,构建beanDefinition,而是通过对应的reader获取,起到解耦作用
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
获取所有资源加载,比如项目中可能存在多个xml文件
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
加载bean 并返回bean总数
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
BeanDefinitionParserDelegate中完成对beanDefinition解析
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
String namespaceUri = getNamespaceURI(ele);
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
上面代码中可以发现解析xml是对应handler处理即接口NamespaceHandler,这个handler可以扩展,后边我们就是基于这个扩展spring标签,并实现bean注册,同时通过PropertyValues可以对BeanDefinition设置属性。
public interface PropertyValues {
PropertyValue[] getPropertyValues();
PropertyValue getPropertyValue(String var1);
PropertyValues changesSince(PropertyValues var1);
boolean contains(String var1);
boolean isEmpty();
}
看懂以上原理,下面实现spring扩展标签比较容易
spring注册bean的形式如下
<bean class="com.stu.code.aspect.AcctService">bean>
现实现一个与bean作用类似的注解自动将属性注入到tableConfig中,这里只是一个介绍原理,并带有一个简单的demo
<table name="acct" type="sharding" />
1、定义TableConfig
/**
* @author Qi.qingshan
* @date 2020/5/2
*/
public class TableConfig {
private String name;
private String type;
//省略get set方法
}
/**
* @author Qi.qingshan
* @date 2020/5/3
*/
public class ShardingConfiguration {
private TableConfig tableConfig;
public TableConfig getTableConfig() {
return tableConfig;
}
public void setTableConfig(TableConfig tableConfig) {
this.tableConfig = tableConfig;
}
}
2、编写XML schema文件,即 .xsd文件,是对xml文件的描述,文件位置可自定义
<xsd:schema xmlns="http://code.stu.com/schema/sharding"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://code.stu.com/schema/sharding"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsd:element name="table">
<xsd:complexType>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="type" type="xsd:string"/>
xsd:complexType>
xsd:element>
xsd:schema>
其中注意,后边注册需要用
xmlns="http://code.stu.com/schema/sharding
3、实现NamespaceHandler
继承NamespaceHandlerSupport即可,在handler中注册parser
/**
* @author Qi.qingshan
* @date 2020/5/2
*/
public class TableNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
/** table为xsd中定义的element*/
registerBeanDefinitionParser("table", new TableBeanDefinitonParser());
}
}
4、实现BeanDefinitionParser
在parser中完成构建BeanDefinition,并注册到容器中
/**
* @author Qi.qingshan
* @date 2020/5/2
*/
public class TableBeanDefinitonParser implements BeanDefinitionParser {
public BeanDefinition parse(Element element, ParserContext parserContext) {
AbstractBeanDefinition definition = SpringBeanExtension.getBeanDefinitionByElement(element);
parserContext.getRegistry().registerBeanDefinition("shardingConfiguration", definition);
return definition;
}
}
public final class SpringBeanExtension {
public static AbstractBeanDefinition getBeanDefinitionByElement(Element element) {
BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(ShardingConfiguration.class);
//ShardingConfiguration 设置tableConfig属性,这里是set方法名
factory.addPropertyValue("tableConfig", parseTableDefinine(element));
return factory.getBeanDefinition();
}
private static BeanDefinition parseTableDefinine(Element element) {
BeanDefinitionBuilder factory = BeanDefinitionBuilder.rootBeanDefinition(TableConfig.class);
//设置属性
factory.addPropertyValue("name", element.getAttribute("name"));
factory.addPropertyValue("type", element.getAttribute("type"));
return factory.getBeanDefinition();
}
}
5、注册Handler和XML schema
在META-INF下新建spring.handlers和sping.schemas文件,内容如下
http\://code.stu.com/schema/sharding=com.stu.spring.handler.TableNamespaceHandler
http\://code.stu.com/schema/sharding/spring-table.xsd=META-INF/spring-table.xsd
6、在spring文件中引入xsd
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sharding="http://code.stu.com/schema/sharding"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.stu.com/schema/sharding
http://code.stu.com/schema/sharding/spring-table.xsd">
<sharding:table name="acct" type="global">sharding:table>
beans>
以xml为例,通过ClassPathXmlApplicationContext加载spring配置
@Test
public void testSringExtension(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
ShardingConfiguration shardingConfiguration = context.getBean(ShardingConfiguration.class);
System.out.println(shardingConfiguration.getTableConfig().getName());
}
本文章对扩展spring标签,实现自定义bean流程做了介绍,可按章节中spring IOC容器分析spring中的实现,或者学习dubbo 或者shardingsphere源码,个人建议看shardingsphere中sharding-spring模块,代码很具有代表性。