菜鸟带你扩展spring标签,实现自定义bean

扩展spring标签

某些场景下咱们须要扩展spring标签,让spring能够识别咱们自定义的标签,实现自定义bean;好比dubbo中定义dubbo:service 等,shardingsphere在适配spring,在spring配置文件中定义分库分表策略等,今天主要分析如何扩展spring自定义标签以及实现demo。为后边自研分库分表中间件实现spring配置作铺垫。web

首先须要简单了解如下信息spring

xsd 与DTD

  • XML Schema 是基于 XML 的 DTD 替代者。编辑器

  • XML Schema 描述 XML 文档的结构。ide

  • XML Schema 语言也称做 XML Schema 定义(XML Schema Definition,XSD)。学习

  • XML Schema 比 DTD 更强大。ui

XSD - 元素

元素是每个 XML Schema 的根元素:this

<?xml version="1.0"?>
 <xs:schema>  ... ...  </xs:schema> 复制代码

元素可包含属性。一个 schema 声明每每看上去相似这样:url

spa

<?xml version="1.0"?> 复制代码<xs:schema xmlns:xs="www.w3.org/2001/XMLSch…" targetNamespace="www.w3school.com.cn" xmlns="www.w3school.com.cn" elementFormDefault="qualified"> ... ... </xs:schema> 复制代码

代码解释:code

下面的片段:

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容器

控制反转或者依赖倒置,对象之间有引用或者依赖关系,由spring 容器来完成,IOC容器实现了对bean管理,因此就涉及到了spring是如何加载bean?以及将bean注册到容器中?

spring对bean的定义接口

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement{}
复制代码

每个bean会构建一个BeanDefinition,构建完成后注册到容器中,实际就是spring维护的HashMap中,主要介绍如何加载BeanDefinition

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);
}
复制代码
复制代码// 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标签实现

实现自定义标签

spring注册bean的形式以下

<bean class="com.stu.code.aspect.AcctService"></bean>
复制代码

现实现一个与bean做用相似的注解自动将属性注入到tableConfig中,这里只是一个介绍原理,并带有一个简单的demo

<table name="acct" type="sharding" />
复制代码

一、定义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; } }  复制代码

二、编写XML schema文件,即 .xsd文件,是对xml文件的描述,文件位置可自定义

<?xml version="1.0" encoding="UTF-8"?>
 <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
复制代码

三、实现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());
    }
}
复制代码

四、实现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(); } }  复制代码

五、注册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
复制代码

六、在spring文件中引入xsd

<?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: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模块,代码很具备表明性。

相关文章
相关标签/搜索