本系列所有基於 Spring 5.2.2.BUILD-SNAPSHOT
版本。由於 Spring 整個體系太過於龐大,因此只會進行關鍵部分的源碼解析。html
本篇文章主要介紹 Spring IoC 容器怎麼解析自定義標籤的。java
在分析自定義標籤怎麼解析以前,咱們先看如何自定義一個標籤以及讓其能被 Spring 解析並加載成 bean
。git
首先編寫一個 users.xsd
文件,其中定義了 User
類型的屬性(該類在Spring IoC BeanDefinition 的加載和註冊一文中定義過);內容以下:github
<xsd:schema xmlns="http://ioc.leisurexi.com/schema/users" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://ioc.leisurexi.com/schema/users"> <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/> <!-- 定義 User 類型(複雜類型) --> <xsd:complexType name="User"> <xsd:attribute name="id" type="xsd:long" use="required"/> <xsd:attribute name="name" type="xsd:string" use="required"/> </xsd:complexType> <!-- 定義 user 元素 --> <xsd:element name="user" type="User"/> </xsd:schema>
注意:上面 XML 中的頭部的地址,我是根據本身的包名來修改的,你須要在
resource
目錄下建立對應的路徑並把 xsd 文件放在該目錄下。spring
編寫 user-context.xml
文件,並在其中使用咱們剛纔的自定義標籤;內容以下:數組
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:users="http://ioc.leisurexi.com/schema/users" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://ioc.leisurexi.com/schema/users http://ioc.leisurexi.com/schema/users.xsd"> <users:user id="1" name="leisurexi"/> </beans>
接下來咱們定義一個類來繼承 AbstractSingleBeanDefinitionParser
重寫標籤的解析方法,以下:緩存
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override protected Class<?> getBeanClass(Element element) { return User.class; } @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { setPropertyValue("id", element, builder); setPropertyValue("name", element, builder); } private void setPropertyValue(String attributeName, Element element, BeanDefinitionBuilder builder) { String attributeValue = element.getAttribute(attributeName); if (StringUtils.hasText(attributeValue)) { builder.addPropertyValue(attributeName, attributeValue); } } }
而後咱們實現 NamespaceHandlerSupport
去註冊元素對應的解析類,以下:app
public class UserNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { // 將 "user" 元素註冊對應的 BeanDefinitionParser 實現 registerBeanDefinitionParser("user", new UserBeanDefinitionParser()); } }
實現類寫好了,但還沒完事;還須要在 META-INF
目錄下建立一個 spring.handlers
文件,寫上命名空間和解析類的映射關係,這是 Spring 中的約定,文件名和目錄得同樣;內容以下:less
http\://ioc.leisurexi.com/schema/users=com.leisurexi.ioc.metadata.UserNamespaceHandler
在 META-INF
建立一個 spring.schemas
文件,這也是約定,文件名和目錄得同樣;內容以下:ide
http\://ioc.leisurexi.com/schema/users.xsd=com/leisurexi/ioc/metadata/users.xsd
接着編寫測試類,看是否能夠獲得咱們定義的 bean
,代碼以下:
public static void main(String[] args) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); reader.loadBeanDefinitions("META-INF/user-context.xml"); User user = beanFactory.getBean(User.class); System.out.println(user); }
最後我把目錄結構貼出來:
注意:
com.leisurexi.ioc.metadata
是層級目錄,最好目錄一個接一個往下建立。
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { // 獲取自定義標籤的命名空間 String namespaceUri = getNamespaceURI(ele); if (namespaceUri == null) { return null; } // 獲取自定義標籤的處理器,見下文詳解 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)); }
上面代碼中的 namespaceUri
就是咱們在 XML 中定義的 http://ioc.leisurexi.com/schema/users
。
public NamespaceHandler resolve(String namespaceUri) { // 命名空間和其處理類的映射 Map<String, Object> handlerMappings = getHandlerMappings(); // 根據命名空間獲取處理類的全類名或其實例 Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } // 若是已是NamespaceHandler類型,表明已經實例化過,直接返回 else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler) handlerOrClassName; } // 沒有對handlerOrClassName實例化,進行實例化,並將實例緩存進handlerMappings else { String className = (String) handlerOrClassName; try { // 根據全類名獲取對應的Class對象 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"); } // 實例化對應的Class對象 NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); // 執行init()方法,也就是在這裏會註冊元素的BeanDefinitionParser實現 namespaceHandler.init(); // 將實例化後的處理類,放入handlerMappings,下次就不用再次建立了 handlerMappings.put(namespaceUri, namespaceHandler); return namespaceHandler; } // 省略異常處理... } }
上面方法中的 getHandlerMappings()
方法返回的就是 spring.handlers
文件中的內容;key
爲命名空間,value
爲全類名。以下所示:
public BeanDefinition parse(Element element, ParserContext parserContext) { // 獲取BeanDefinition解析器 BeanDefinitionParser parser = findParserForElement(element, parserContext); // 進行標籤的解析,見下文詳解 return (parser != null ? parser.parse(element, parserContext) : null); } private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { // 獲取標籤元素,好比咱們自定義的 <users:user id="1" name="leisurexi"/>,這裏獲取的就是user String localName = parserContext.getDelegate().getLocalName(element); // 根據名稱獲取解析器 BeanDefinitionParser parser = this.parsers.get(localName); if (parser == null) { parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element); } return parser; }
上面方法中的 parsers
中的內容,就是上面 UserNamespaceHandler
類中重寫 init()
方法中註冊的解析器。
public final BeanDefinition parse(Element element, ParserContext parserContext) { // 獲取調用自定義解析方法後的BeanDefinition,見下文詳解 AbstractBeanDefinition definition = parseInternal(element, parserContext); if (definition != null && !parserContext.isNested()) { try { // 獲取id屬性,若是有的話,沒有的話就生成一個默認的id String id = 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); } String[] aliases = null; if (shouldParseNameAsAliases()) { // 獲取name屬性 String name = element.getAttribute(NAME_ATTRIBUTE); if (StringUtils.hasLength(name)) { // 若是name屬性不爲空,根據逗號分割成數組做爲別名 aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); } } // 用definition、id和aliases構建成BeanDefinitionHolder BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases); // 註冊BeanDefinition registerBeanDefinition(holder, parserContext.getRegistry()); if (shouldFireEvents()) { // 發送bean註冊完成事件 BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder); postProcessComponentDefinition(componentDefinition); parserContext.registerComponent(componentDefinition); } } catch (BeanDefinitionStoreException ex) { String msg = ex.getMessage(); parserContext.getReaderContext().error((msg != null ? msg : ex.toString()), element); return null; } } return definition; }
上面方法中的 resolveId()
方法會獲取標籤中的 id
屬性,若是沒有的話會調用 BeanDefinitionReaderUtils.generateBeanName()
來生成一個,該方法和 registerBeanDefinition()
方法在Spring IoC 默認標籤解析一文中有介紹。
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); // 獲取父bean名稱 String parentName = getParentName(element); if (parentName != null) { builder.getRawBeanDefinition().setParentName(parentName); } // 獲取bean的類型,這裏就會調用UserBeanDefinitionParser重寫的getBeanClass()方法 Class<?> beanClass = getBeanClass(element); if (beanClass != null) { // 設置進BeanDefinition中 builder.getRawBeanDefinition().setBeanClass(beanClass); } // 若是沒有重寫getBeanClass()方法,就調用getBeanClassName()方法 else { String beanClassName = getBeanClassName(element); if (beanClassName != null) { builder.getRawBeanDefinition().setBeanClassName(beanClassName); } } builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); // 獲取當前BeanDefinition的外層BeanDefinition BeanDefinition containingBd = parserContext.getContainingBeanDefinition(); if (containingBd != null) { // 內嵌bean必需和外層bean有相同的做用域 builder.setScope(containingBd.getScope()); } if (parserContext.isDefaultLazyInit()) { builder.setLazyInit(true); } // 調用子類重寫的doParse()方法 doParse(element, parserContext, builder); return builder.getBeanDefinition(); }
上面方法中 doParse()
方法就會調用咱們自定義的 UserBeanDefinitionParser
類中的 doParse()
方法。
本文主要介紹了 Spring 對 XML 文件中自定義標籤的解析,咱們能夠從新梳理一下思路:
NamespaceHandler
。BeanDefinitionParser
。doParse()
方法來構建 BeanDefinition
。BeanDefinition
以及發送 bean
註冊完成事件。最後,我模仿 Spring 寫了一個精簡版,代碼會持續更新。地址:https://github.com/leisurexi/tiny-spring。