Spring源碼解密之自定義標簽與解析
前言
在 上一節(jié) Spring解密 - 默認標簽的解析 中,重點分析了 Spring 對默認標簽是如何解析的,那么本章繼續(xù)講解標簽解析,著重講述如何對自定義標簽進行解析。話不多說了,來一起看看詳細的介紹吧。
自定義標簽
在講解 自定義標簽解析 之前,先看下如何自定義標簽
定義 XSD 文件
定義一個 XSD 文件描述組件內(nèi)容
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns="http://www.battcn.com/schema/battcn" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" targetNamespace="http://www.battcn.com/schema/battcn" elementFormDefault="qualified" attributeFormDefault="unqualified"> <xsd:import namespace="http://www.springframework.org/schema/beans" /> <xsd:element name="application"> <xsd:complexType> <xsd:complexContent> <xsd:extension base="beans:identifiedType"> <xsd:attribute name="name" type="xsd:string" use="required"/> </xsd:extension> </xsd:complexContent> </xsd:complexType> </xsd:element> </xsd:schema>
- 聲明命名空間: 值得注意的是 xmlns 與 targetNamespace 可以是不存在,只要映射到指定 XSD 就行了。
- 定義復合元素: 這里的 application 就是元素的名稱,使用時 <battcn:application id="battcn"/>
- 定義元素屬性: 元素屬性就是 attribute 標簽,我們聲明了一個必填的 name 的屬性,使用時 <battcn:application id="battcn" name="Levin"/>
定義解析規(guī)則
1.創(chuàng)建一個類實現(xiàn) BeanDefinitionParser 接口(也可繼承 Spring 提供的類),用來解析 XSD 文件中的定義和組件定義
public class ApplicationBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
@Override
protected Class getBeanClass(Element element) {
// 接收對象的類型 如:String name = (String) context.getBean("battcn");
return String.class;
}
@Override
protected void doParse(Element element, BeanDefinitionBuilder bean) {
// 在 xsd 中定義的 name 屬性
String name = element.getAttribute("name");
bean.addConstructorArgValue(name);
}
}
這里創(chuàng)建了一個 ApplicationBeanDefinitionParser 繼承 AbstractSingleBeanDefinitionParser(是:BeanDefinitionParser 的子類), 重點就是重寫的 doParse,在這個里面解析 XML 標簽的,然后將解析出的 value(Levin) 通過構造器方式注入進去
2.創(chuàng)建一個類繼承 NamespaceHandlerSupport 抽象類
public class BattcnNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("application", new ApplicationBeanDefinitionParser());
}
}
BattcnNamespaceHandler 的作用特別簡單,就是告訴 Spring 容器,標簽 <battcn:application /> 應該由那個解析器解析(這里是我們自定義的:ApplicationBeanDefinitionParser),負責將組件注冊到 Spring 容器
3.編寫 spring.handlers 和 spring.schemas 文件
文件存放的目錄位于 resources/META-INF/文件名
spring.handlers
http\://www.battcn.com/schema/battcn=com.battcn.handler.BattcnNamespaceHandler
spring.schemas
http\://www.battcn.com/schema/battcn.xsd=battcn.xsd
4.使用自定義標簽
申明 bean.xml 文件,定義如下
<?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:battcn="http://www.battcn.com/schema/battcn" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.battcn.com/schema/battcn http://www.battcn.com/schema/battcn.xsd"> <battcn:application id="battcn" name="Levin"/> </beans>
創(chuàng)建一個測試類,如果看到控制臺輸出了 Levin 字眼,說明自定義標簽一切正常
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
String name = (String) context.getBean("battcn");
System.out.println(name);
}
}
5.如圖所示

源碼分析
自定義標簽解析入口
public class BeanDefinitionParserDelegate {
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// 獲取命名空間地址 http://www.battcn.com/schema/battcn
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// NamespaceHandler 就是 自定義的 BattcnNamespaceHandler 中注冊的 application
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));
}
}
與默認標簽解析規(guī)則一樣的是,都是通過 getNamespaceURI(Node node) 來獲取命名空間,那么 this.readerContext.getNamespaceHandlerResolver() 是從哪里獲取的呢?我們跟蹤下代碼,可以發(fā)現(xiàn)在項目啟動的時候,會在 XmlBeanDefinitionReader 將所有的 META-INF/spring.handles 文件內(nèi)容解析,存儲在 handlerMappers(一個ConcurrentHashMap) 中,在調(diào)用 resolve(namespaceUri) 校驗的時候在將緩存的內(nèi)容提取出來做對比
public class XmlBeanDefinitionReader {
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
}
resolve
1.加載指定的 NamespaceHandler 映射,并且提取的 NamespaceHandler 緩存起來,然后返回
public class DefaultNamespaceHandlerResolver {
@Override
@Nullable
public NamespaceHandler resolve(String namespaceUri) {
Map<String, Object> handlerMappings = getHandlerMappings();
// 從 handlerMappings 提取 handlerOrClassName
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
String className = (String) handlerOrClassName;
try {
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");
}
// 根據(jù)命名空間尋找對應的信息
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// Handler 初始化
namespaceHandler.init();
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);
}
}
}
}
標簽解析
加載完 NamespaceHandler 之后,BattcnNamespaceHandler 就已經(jīng)被初始化為 了,而 BattcnNamespaceHandler 也調(diào)用了 init() 方法完成了初始化的工作。因此就接著執(zhí)行這句代碼: handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); 具體標簽解。
public class NamespaceHandlerSupport {
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionParser parser = findParserForElement(element, parserContext);
return (parser != null ? parser.parse(element, parserContext) : null);
}
@Nullable
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
// 解析出 <battcn:application /> 中的 application
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 中尋找到 ApplicationBeanDefinitionParser 實例,并調(diào)用其自身的 doParse 方法進行進一步解析。最后就跟解析默認標簽的套路一樣了…
總結
熬過幾個無人知曉的秋冬春夏,撐過去一切都會順著你想要的方向走…
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
說點什么
全文代碼:https://gitee.com/battcn/battcn-spring-source/tree/master/Chapter2
相關文章
SpringBoot+Redis+Lua分布式限流的實現(xiàn)
本文主要介紹了SpringBoot+Redis+Lua分布式限流的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-08-08
Javaweb實現(xiàn)在線人數(shù)統(tǒng)計代碼實例
這篇文章主要介紹了Javaweb實現(xiàn)在線人數(shù)統(tǒng)計代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-11-11

