菜鳥帶你擴展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模塊,代碼很具備表明性。

相關文章
相關標籤/搜索