javascript
Spring源码解析:自定义标签的解析过程
2019獨角獸企業重金招聘Python工程師標準>>>
spring version : 4.3.x
Spring 中的標簽分為默認標簽和自定義標簽兩類,上一篇我們探究了默認標簽的解析過程,當然在閱讀源碼的過程中我們也看到默認標簽的解析過程中嵌套了對自定義標簽的解析,這是因為默認標簽中可以嵌套使用自定義標簽,但是這和本篇所要討論的自定義標簽還是有些區別的,上一篇中介紹的自定義標簽可以看做是 <bean/> 標簽的子標簽元素,而本篇所指的標簽是與 <bean/> 這類標簽平級的自定義標簽。
一. 自定義標簽的定義和使用方式
在具體開挖源碼之前,我們還是來回憶一下自定義標簽的定義和使用方式,整體上與上一篇 1.2 小節所定義的方式類似,但還是有些許差別。要自定義標簽,分為 5 步:
這里我們自定義實現一個類似 <alias/> 功能的標簽,來為指定的 bean 添加別名。第一步,先創建標簽對應的實體:
public class Alias {private String name;private String alias;// 省略 getter 和 setter }第二步,定義標簽的 XSD 文件 custom-alias.xsd:
<?xml version="1.0" encoding="UTF-8"?> <schema xmlns="http://www.w3.org/2001/XMLSchema"targetNamespace="http://www.zhenchao.org/schema/alias"xmlns:tns="http://www.zhenchao.org/schema/alias"elementFormDefault="qualified"><element name="alias"><complexType><attribute name="id" type="string"/><attribute name="name" type="string"/><attribute name="parentName" type="string"/><attribute name="c_name" type="string"/><attribute name="c_alias" type="string"/></complexType></element> </schema>第三步,創建標簽元素解析器,實現 BeanDefinitionParser 接口,這里我們繼承該接口的子接口 AbstractSingleBeanDefinitionParser,并覆蓋對應的方法:
public class CustomBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {@Overrideprotected Class<?> getBeanClass(Element element) {return Alias.class;}@Overrideprotected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {String beanName = element.getAttribute("c_name");Assert.hasText(beanName, "The 'name' in alias tag is missing!");Assert.isTrue(parserContext.getRegistry().containsBeanDefinition(beanName), "No bean name " + beanName + " exist!");String alias = element.getAttribute("c_alias");Assert.hasText(beanName, "The 'alias' in alias tag is missing!");String[] aliasArray = alias.replaceAll("\\s+", "").split("[,;]");for (final String ali : aliasArray) {parserContext.getRegistry().registerAlias(beanName, ali);}} }方法中的邏輯先判斷對應的 beanName 是否存在,如果存在的話就建立 beanName 與 alias 之間的映射關系。
第四步,創建標簽 handler 類,繼承自 NamespaceHandlerSupport,用于注冊第三步中定義的標簽解析器:
public class CustomNamespaceHandler extends NamespaceHandlerSupport {@Overridepublic void init() {this.registerBeanDefinitionParser("alias", new CustomBeanDefinitionParser());}}第五步,編寫 spring.handlers 和 spring.schemas 文件:
spring.handlers
http://www.zhenchao.org/schema/alias=org.zhenchao.handler.CustomNamespaceHandler
spring.schemas
http://www.zhenchao.org/schema/alias.xsd=META-INF/custom-alias.xsd
接下來演示一下上述自定義標簽的使用方式,首先需要在 <beans/> 標簽屬性中定義標簽的命名空間:
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:myalias="http://www.zhenchao.org/schema/alias"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.zhenchao.org/schema/alias http://www.zhenchao.org/schema/alias.xsd"然后使用我們自定義的標簽為已定義的 bean 添加別名:
<!-- my-parent-bean 是一個已定義的 bean --> <myalias:alias id="my-alias" c_name="my-parent-bean" c_alias="aaa; bbb"/>這樣我們完成了利用自定義的標簽為 my-parent-bean 添加別名,接下來我們開挖自定義標簽的解析過程。
二. 自定義標簽的解析過程
再來回顧一下我們開始解析標簽的入口函數 parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate):
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {if (delegate.isDefaultNamespace(root)) {// 解析默認標簽(beans標簽)NodeList nl = root.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (node instanceof Element) {Element ele = (Element) node;if (delegate.isDefaultNamespace(ele)) {// 解析默認標簽(子級嵌套)this.parseDefaultElement(ele, delegate);} else {// 解析自定義標簽(子級嵌套)delegate.parseCustomElement(ele);}}}} else {// 解析自定義標簽delegate.parseCustomElement(root);} }上一篇中我們探究了默認標簽的解析過程,也就是 parseDefaultElement(Element element, BeanDefinitionParserDelegate delegate) 方法,接下來我們來探究自定義標簽的解析過程,及 parseCustomElement(Element ele) 方法:
public BeanDefinition parseCustomElement(Element ele) {return this.parseCustomElement(ele, null); } public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {// 獲取標簽的命名空間String namespaceUri = this.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)); }上述方法首先會去獲取自定義標簽的命名空間定義,然后基于命名空間解析得到對應的 NamespaceHandler,最后調用 handler 對標簽進行解析處理,本質上調用的就是前面自定義實現的 doParse() 方法。我們先來看一下自定義標簽 NamespaceHandler 的解析過程,位于 DefaultNamespaceHandlerResolver 的 resolve(String namespaceUri) 方法中:
public NamespaceHandler resolve(String namespaceUri) {// 獲取所有已注冊的handler集合Map<String, Object> handlerMappings = this.getHandlerMappings();// 獲取namespaceUri對應的handler全程類名或handler實例Object handlerOrClassName = handlerMappings.get(namespaceUri);if (handlerOrClassName == null) {return null;} else if (handlerOrClassName instanceof NamespaceHandler) {// 已經解析過,直接返回handler實例return (NamespaceHandler) handlerOrClassName;} else {// 未做過解析,則解析對應的類路徑classNameString className = (String) handlerOrClassName;try {// 使用反射創建handler實例Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");}// 初始化實例NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);// 調用init()方法namespaceHandler.init();// 緩存解析后的handler實例handlerMappings.put(namespaceUri, namespaceHandler);return namespaceHandler;} catch (ClassNotFoundException ex) {throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found", ex);} catch (LinkageError err) {throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]: problem with handler class file or dependent class", err);}} }方法中的邏輯可以概括如下:
上述過程中第 4 步稍微復雜一點,我們來看一下具體過程。我們在 spring.handlers 中會配置 namespaceUri 與對應 handler 全稱類名的鍵值對:
http://www.zhenchao.org/schema/alias=org.zhenchao.handler.CustomNamespaceHandler
這里會拿到對應 handler 的全稱類名,然后基于反射來創建 handler 實例,過程中會設置構造方法為 accessible。接下來就是輪到第五步中的調用 init() 方法,這個方法是由開發人員自己實現的,我們前面的例子中通過該方法將我們自定義的解析器 CustomBeanDefinitionParser 注冊到 handler 實例中。接下來就是調用 handler 實例處理自定義標簽:
public BeanDefinition parse(Element element, ParserContext parserContext) {// 尋找解析器并進行解析return this.findParserForElement(element, parserContext) // 找到對應的解析器.parse(element, parserContext); // 進行解析(這里的解析過程是開發者自定義實現的) }這里主要分為 獲取 handler 實例 和 執行解析 兩個步驟,其中獲取 handler 實例就是依據我們使用的標簽名從之前的緩存 map 中拿到對應的對象,然后調用 handler 的 parse(Element element, ParserContext parserContext) 方法執行解析邏輯:
public final BeanDefinition parse(Element element, ParserContext parserContext) {// 1. 創建自定義標簽BeanDefinition實例,并調用自定義解析器進行解析處理AbstractBeanDefinition definition = this.parseInternal(element, parserContext);if (definition != null && !parserContext.isNested()) { // nested:嵌套的// definition實例存在且不是嵌套的try {// 2. 獲取標簽的id屬性,id屬性是必備的String id = this.resolveId(element, definition, parserContext);if (!StringUtils.hasText(id)) {parserContext.getReaderContext().error("Id is required for element '" + parserContext.getDelegate().getLocalName(element) + "' when used as a top-level tag", element);}// 3. 獲取name字段String[] aliases = null;if (this.shouldParseNameAsAliases()) {String name = element.getAttribute(NAME_ATTRIBUTE);if (StringUtils.hasLength(name)) {aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));}}// 4. 將AbstractBeanDefinition轉化成BeanDefinitionHolder并注冊BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);this.registerBeanDefinition(holder, parserContext.getRegistry());// 5. 事件通知if (this.shouldFireEvents()) {BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder);this.postProcessComponentDefinition(componentDefinition);parserContext.registerComponent(componentDefinition);}} catch (BeanDefinitionStoreException ex) {parserContext.getReaderContext().error(ex.getMessage(), element);return null;}}return definition; }上述方法中的第一步是整個方法的核心,我們后面細講,先來看一下第二、三步驟,對于自定義標簽來說,id 屬性是必備的,此外 Spring 還內置了 name 和 parentName 字段,這些名稱是不允許使用的,否則達不到我們預期的結果,筆者第一次使用自定義標簽時就踩了坑,用了 name 作為自定義標簽屬性名,結果就是各種奇怪的結果。
接下來看看第一步的邏輯,位于 AbstractSingleBeanDefinitionParser 的 parseInternal(Element element, ParserContext parserContext) 方法中:
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {// 初始化自定義標簽實例BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();// 如果設置了parentNameString parentName = this.getParentName(element);if (parentName != null) {builder.getRawBeanDefinition().setParentName(parentName);}// 調用自定義BeanDefinitionParser中的getBeanClass方法Class<?> beanClass = this.getBeanClass(element);if (beanClass != null) {builder.getRawBeanDefinition().setBeanClass(beanClass);} else {// 如果自定義解析器沒有重寫getBeanClass方法,則檢查子類是否重寫了getBeanClassName方法String beanClassName = this.getBeanClassName(element);if (beanClassName != null) {builder.getRawBeanDefinition().setBeanClassName(beanClassName);}}builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));if (parserContext.isNested()) {// 如果當前標簽是嵌套的,則使用父類的scope屬性builder.setScope(parserContext.getContainingBeanDefinition().getScope());}// 設置延遲加載if (parserContext.isDefaultLazyInit()) {builder.setLazyInit(true);}// 調用自定義解析器覆蓋的doParse方法進行解析this.doParse(element, parserContext, builder);// 返回自定義標簽的beanDefinition實例return builder.getBeanDefinition(); }上述方法中首先會初始化創建一個 BeanDefinitionBuilder 對象,然后依據配置設置對象的相應屬性,其中包括調用我們之前在實現自定義標簽解析器 CustomBeanDefinitionParser 時候覆蓋的 getBeanClass 方法。然后會調用 doParse 方法,該方法由開發者實現,也是我們解析自定義標簽的核心方法:
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {String beanName = element.getAttribute("c_name");Assert.hasText(beanName, "The 'name' in alias tag is missing!");Assert.isTrue(parserContext.getRegistry().containsBeanDefinition(beanName), "No bean name " + beanName + " exist!");String alias = element.getAttribute("c_alias");Assert.hasText(beanName, "The 'alias' in alias tag is missing!");String[] aliasArray = alias.replaceAll("\\s+", "").split("[,;]");for (final String ali : aliasArray) {parserContext.getRegistry().registerAlias(beanName, ali);} }最后,返回自定義標簽對應的 beanDefinition 實例。
分析到這里,Spring 對于配置文件的解析工作已經做完了,容器將一個個 bean 的靜態配置解析映射稱為 beanDefinition 實例,并注冊到容器的 Map 集合中,剩下的就是對 bean 實例的創建和初始化過程了,我們在下一篇中對這一過程的具體實現進行詳細探究。
系列文章
鑒于作者水平有限,文中不免有錯誤之處,歡迎大家批評指正~
同步更新站點:www.zhenchao.org
轉載于:https://my.oschina.net/wangzhenchao/blog/917481
總結
以上是生活随笔為你收集整理的Spring源码解析:自定义标签的解析过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 高考带给了我们什么..........
- 下一篇: EXCEL 列与列怎么交换?