??在之前的章節(jié)中,我們提到了在
Spring
中存在默認(rèn)標(biāo)簽與自定義標(biāo)簽兩種,而在上一章節(jié)中我們分析了Spring
中自定義標(biāo)簽的加載過(guò)程.同樣,我們還是先再次回顧一下,當(dāng)完成從配置文件到Document
的轉(zhuǎn)換并提取對(duì)應(yīng)的root
后,將開(kāi)始了所有元素的解析,而在這一過(guò)程中便開(kāi)始了默認(rèn)標(biāo)簽與自定義標(biāo)簽兩中格式的區(qū)分,方法如下:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
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)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
??在本章中,所有的功能都是圍繞其中的一句代碼
delegate.parseCustomElement(root)
開(kāi)展的.從上面的函數(shù)我們可以看出,當(dāng)Spring
拿到一個(gè)元素時(shí)首先要做的是根據(jù)命名空間進(jìn)行解析,如果是默認(rèn)的命名空間,則使用parseDefaultElement
方法進(jìn)行元素解析,否則使用parseCustomElement
方法進(jìn)行解析.在分析自定義標(biāo)簽的解析過(guò)程前,我們先了解一下自定義標(biāo)簽的使用過(guò)程.
4.1 自定義標(biāo)簽使用
??在很多情況下,我們需要為系統(tǒng)提供可配置化支持,簡(jiǎn)單的做法可以直接基于
Spring
的標(biāo)準(zhǔn)bean
來(lái)配置,但配置較為復(fù)雜或者需要更多豐富控制的時(shí)候,會(huì)顯得非常笨拙.一般的做法會(huì)用原生態(tài)的方式去解析定義好的XML
文件,然后轉(zhuǎn)化為配置對(duì)象.這種方式當(dāng)然可以解決所有問(wèn)題,但實(shí)現(xiàn)起來(lái)比較繁瑣,特別是在配置非常復(fù)雜的時(shí)候,解析工作是一個(gè)不得不考慮的負(fù)擔(dān).Spring
提供了可擴(kuò)展Schema
的支持,這個(gè)一個(gè)不錯(cuò)的折中方案,擴(kuò)展Spring
自定義標(biāo)簽配置大致需要以下幾個(gè)步驟(前提是要把Spring
的Core
包加入項(xiàng)目中).
- 創(chuàng)建一個(gè)需要擴(kuò)展的組件.
- 定義一個(gè)
XSD
文件描述組件內(nèi)容.- 創(chuàng)建一個(gè)文件,實(shí)現(xiàn)
BeanDefinitionParser
接口,用來(lái)解析XSD
文件中的定義和組件定義.- 創(chuàng)建一個(gè)
Handler
文件,擴(kuò)展自NamespaceHandlerSupport
,目的是將組件注冊(cè)到Spring
容器.- 編寫
Spring.handlers
和Spring.schemas
文件.?現(xiàn)在我們就按照上面的步驟和大家一起體驗(yàn)自定義標(biāo)簽的過(guò)程.
(1)首先我們創(chuàng)建一個(gè)普通的
POJO
,這個(gè)POJO
沒(méi)有任何特別之處,只是用來(lái)接收配置文件.
public class User {
private String userName;
private String email;
// 省略get/set方法
}
(2)定義一個(gè)
XSD
文件描述組件內(nèi)容.
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.test.com/schema/user"
xmlns:tns="http://www.test.com/schema/user"
elementFormDefault="qualified">
<element name="user">
<complexType>
<attribute name="id" type="string" />
<attribute name="userName" type="string" />
<attribute name="email" type="string" />
</complexType>
</element>
</schema>
??在上面的
XSD
文件中描述了一個(gè)新的targetNamespace
,并在這個(gè)空間中定義了一個(gè)name
為user
的element
,user
有3個(gè)屬性id
,userName
和string
.這3個(gè)類主要用于驗(yàn)證Spring
配置文件中自定義格式.XSD
文件是XML
,DTD
的替代者,使用XML Schema
語(yǔ)言進(jìn)行編寫,這里對(duì)XSD Schema
不做太多解釋,大家有興趣可以自己研究一下.
(3)創(chuàng)建一個(gè)文件,實(shí)現(xiàn)
BeanDefinitionParser
接口,用來(lái)解析XSD
文件中的定義和組件定義.
public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
// Element對(duì)應(yīng)的類
protected Class getBeanClass(Element element) {
return User.class;
}
// 從element中解析并提取對(duì)應(yīng)的元素
protected void doParse(Element element, BeanDefinitionBuilder bean) {
String userName = element.getAttribute("userName");
String email = element.getAttribute("email");
// 將提取的數(shù)據(jù)放入到BeanDefinitionBuilder中,待到完成所有bean的解析后統(tǒng)一注冊(cè)到beanFatory中
if (StringUtils.hasText(userName)) {
bean.addPropertyValue("userName", userName);
}
if (StringUtils.hasText(email)) {
bean.addPropertyValue("email", email);
}
}
}
(4) 創(chuàng)建一個(gè)
Handler
文件,擴(kuò)展自NamespaceHandlerSupport
,目的是將組件注冊(cè)到Spring
容器.
public class MyNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}
}
??以上代碼很簡(jiǎn)單,無(wú)非是當(dāng)遇到自定義標(biāo)簽
<user:aaa>
這樣類似于以user
開(kāi)頭的元素,就會(huì)把這個(gè)元素扔給對(duì)應(yīng)的UserBeanDefinitionParser
去解析.
(5) 編寫
Spring.handlers
和Spring.schemas
文件,默認(rèn)位置是在工程的/ META-INF/
文件夾下,當(dāng)然,你可以通過(guò)Spring
的擴(kuò)展或者修改源碼的方式改變路徑.
- Spring.handlers :
http\://www.test.com/schema/user=test.MyNamespaceHandler
- Spring.schema :
http\://www.test.com/schema/user.xsd=META-INF/Spring-test.xsd
?到這里,自定義的配置就結(jié)束了,而
Spring
加載自定義的大致流程是遇到自定義標(biāo)簽然后就去Spring.handlers
和Spring.schemas
中去找對(duì)應(yīng)的handler
和XSD
,默認(rèn)位置是/META-INF/
下,進(jìn)而有找到對(duì)應(yīng)的handler
以及解析元素的Parser
,從而完成整個(gè)自定義元素的解析,也就是說(shuō)自定義與Spring
中默認(rèn)的標(biāo)準(zhǔn)配置不同在于Spring
將自定義標(biāo)簽解析的工作委托給了用戶去實(shí)現(xiàn).
(6) 創(chuàng)建測(cè)試配置文件,在配置文件中引入對(duì)應(yīng)的命名空間以及
XSD
后,便可以直接使用自定義標(biāo)簽了.
<?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:myname="http://www.test.com/schema/user"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.test.com/schema/user http://www.test.com/scheame/user.xsd">
<myname:user id="testbean" userName="aaa" email="bbb" />
</beans>
(7) 測(cè)試
public class test {
public static void main(String[] args) {
ApplicationContext bf = new ClassPathXmlApplicationContext("test/test.xml");
User user = (User) bf.getBean("testbean");
System.out.println(user.getUserName() + ", " + user.getEmail());
}
}
?不出意外的話,應(yīng)該可以看到控制臺(tái)打印了如下結(jié)果:
?aaa
,bbb
?在上面的例子中,我們實(shí)現(xiàn)了通過(guò)自定義標(biāo)簽通過(guò)屬性的方式將user
類型的Bean
賦值,在Spring
中自定義標(biāo)簽非常常用,例如我們熟知的事物標(biāo)簽:tx(<tx:annotation-driven>)
.
4.2 自定義標(biāo)簽解析
??了解了自定義標(biāo)簽的使用后,我們帶著強(qiáng)烈的好奇心來(lái)探究一下自定義的解析過(guò)程.
@Nullable
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
// containingBd為父類bean,對(duì)頂層元素的解析應(yīng)該設(shè)置為null
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// 獲取對(duì)應(yīng)的命名空間
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 根據(jù)命名空間找到對(duì)應(yīng)的NamespaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 調(diào)用自定義的NamespaceHandler進(jìn)行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
??相信了解了自定義標(biāo)簽的使用方法后,或多或少會(huì)對(duì)自定義標(biāo)簽的實(shí)現(xiàn)過(guò)程有一個(gè)自己的想法.其實(shí)實(shí)現(xiàn)思路非常的簡(jiǎn)單,無(wú)非是根據(jù)對(duì)應(yīng)的
bean
獲取對(duì)應(yīng)的命名空間,根據(jù)命名空間解析對(duì)應(yīng)的處理器,然后根據(jù)用戶自定義的處理器進(jìn)行解析.可是有些事情說(shuō)起來(lái)簡(jiǎn)單做起來(lái)難,我們接下來(lái)先看看如何獲取命名空間.
4.2.1 獲取標(biāo)簽的命名空間
??標(biāo)簽的解析是從命名空間的提起開(kāi)始的,無(wú)論是區(qū)分
Spring
中默認(rèn)標(biāo)簽和自定義標(biāo)簽還是區(qū)分自定義標(biāo)簽中不同標(biāo)簽的處理器都是以標(biāo)簽所提供的命名空間為基礎(chǔ)的,而至于如何提取對(duì)應(yīng)元素的命名空間其實(shí)不需要我們親自去實(shí)現(xiàn),在org.w3c.dom.Node
中已經(jīng)提供了方法供我們直接調(diào)用:
public String getNamespaceURI(Node node){
return node.getNamespaceURI();
}
4.2.2 提取自定義標(biāo)簽處理器
??有了命名空間,就可以進(jìn)行
NamespaceHandler
的提取了,繼續(xù)之前的parseCustomElement
方法的跟蹤,分析NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
,在readerContext
初始化的時(shí)候其屬性namespaceHandlerResolver
已經(jīng)被初始化為了DefaultNamespaceHandlerResolver
的實(shí)例,所以這里調(diào)用的resolve
方法其實(shí)調(diào)用的是DefaultNamespaceHandlerResolver
類中的方法.我們進(jìn)入DefaultNamespaceHandlerResolver
的resolve
方法進(jìn)行查看.
@Override
@Nullable
public NamespaceHandler resolve(String namespaceUri) {
// 獲取所有已經(jīng)配置的handler映射
Map<String, Object> handlerMappings = getHandlerMappings();
// 根據(jù)命名空間找到對(duì)應(yīng)的信息
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
// 已經(jīng)做過(guò)解析的情況直接從緩存讀取
return (NamespaceHandler) handlerOrClassName;
}
else {
// 沒(méi)有做過(guò)解析則返回類路徑
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");
}
// 初始化類
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 調(diào)用自定義的NamespaceHandler的初始化方法
namespaceHandler.init();
// 記錄在緩存中
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
"] for namespace [" + namespaceUri + "]", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
className + "] for namespace [" + namespaceUri + "]", err);
}
}
}
??上面的函數(shù)清晰地闡述了解析自定義
NamespaceHandler
的過(guò)程,通過(guò)之前的示例程序我們了解到如果要使用自定義標(biāo)簽,那么其中一項(xiàng)必不可少的操作就是在Spring.handlers
文件中配置命名空間與命名空間處理的映射關(guān)系.只有這樣,Spring
才能根據(jù)映射關(guān)系找到匹配的處理器,而尋找匹配的處理器就是在上面方法中實(shí)現(xiàn),當(dāng)獲取到自定義的NamespaceHandler
之后就可以進(jìn)行處理器初始化并解析了.我們這里在回憶一下對(duì)命名空間處理器的定義內(nèi)容:
public class MyNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("user", new UserBeanDefinitionParser());
}
}
??當(dāng)?shù)玫阶远x命名空間處理后會(huì)馬上執(zhí)行
namespaceHandler.init()
來(lái)進(jìn)行自定義BeanDefinitionParser
的注冊(cè).在這里,可以注冊(cè)多個(gè)標(biāo)簽解釋器,當(dāng)前實(shí)例中只有支持<myname:user>
的寫法,也可以在這里注冊(cè)多個(gè)解析器,如<myname:A> <myname:B>
等,是的myname
的命名空間中可以支持多種標(biāo)簽解析.
??注冊(cè)后,命名空間處理器就可以根據(jù)標(biāo)簽的不同來(lái)調(diào)用不同的解析器進(jìn)行解析.那么,根據(jù)上面的函數(shù)與之前介紹過(guò)的例子,我們基本上可以推斷getHandlerMappings
的主要功能就是讀取Spring.handlers
配置文件并將配置文件緩存在map
中.
private Map<String, Object> getHandlerMappings() {
Map<String, Object> handlerMappings = this.handlerMappings;
// 如果沒(méi)有被緩存則開(kāi)始進(jìn)行緩存
if (handlerMappings == null) {
synchronized (this) {
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
if (logger.isDebugEnabled()) {
logger.debug("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
}
try {
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}
handlerMappings = new ConcurrentHashMap<>(mappings.size());
// 將Properties格式文件合并到Map格式的handlerMappings中
CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
}
}
}
}
return handlerMappings;
}
??同我們想象的一樣,接住了工具類
PropertiesLoaderUtils
對(duì)屬性handlerMappingsLocation
進(jìn)行了配置文件的讀取,handlerMappingsLocation
被默認(rèn)初始化為META-INF/Spring.handlers
.
4.2.3 標(biāo)簽解析
??得到了解析器以及要分析的元素后,
Spring
就可以將解析工作委托給自定義解析器去解析了.在Spring
中的代碼為:
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
??以之前提到的示例進(jìn)行分析,此時(shí)的
handler
已經(jīng)被實(shí)例化成我們自定義的MyNamespaceHandler
了,而MyNamespaceHandler
也已經(jīng)完成了初始化的工作,但是在我們實(shí)現(xiàn)的自定義命名空間處理器中并沒(méi)有實(shí)現(xiàn)parse
方法,所以推斷,這個(gè)方法是父類中的實(shí)現(xiàn),查看父類NamespaceHandlerSupport
中的parse
方法.
@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 尋找解析器并進(jìn)行解析操作
BeanDefinitionParser parser = findParserForElement(element, parserContext);
return (parser != null ? parser.parse(element, parserContext) : null);
}
??解析過(guò)程中首先是尋找元素對(duì)應(yīng)的解析器,進(jìn)而調(diào)用解析器中的
parse
方法,那么結(jié)合示例來(lái)講,其實(shí)就是首先獲取在MyNameSpaceHandler
類中的init
方法中注冊(cè)的對(duì)應(yīng)的UserBeanDefinitionParser
實(shí)例,并調(diào)用其parse
方法進(jìn)行進(jìn)一步解析.
@Nullable
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
// 獲取元素名稱,也就是<myname:user中的user,若在示例中,此時(shí)localName為user
String localName = parserContext.getDelegate().getLocalName(element);
// 根據(jù)user找到對(duì)應(yīng)的解析器,也就是在registerBeanDefinitionParser("user", new UserBeanDefinitionParser());注冊(cè)的解析器
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}
而對(duì)于
parse
方法的處理
@Override
@Nullable
public final BeanDefinition parse(Element element, ParserContext parserContext) {
AbstractBeanDefinition definition = parseInternal(element, parserContext);
if (definition != null && !parserContext.isNested()) {
try {
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()) {
String name = element.getAttribute(NAME_ATTRIBUTE);
if (StringUtils.hasLength(name)) {
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
}
}
// 將AbstractBeanDefinition轉(zhuǎn)化為BeanDefinitionHolder并注冊(cè)
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
registerBeanDefinition(holder, parserContext.getRegistry());
if (shouldFireEvents()) {
// 需要通知監(jiān)聽(tīng)器則進(jìn)行處理
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;
}
??雖說(shuō)是對(duì)自定義配置文件的解析,但是,我們可以看到,在這個(gè)方法中大部分的代碼是用來(lái)處理將解析后的
AbstractBeanDefinition
轉(zhuǎn)化為BeanDefinitionHolder
并注冊(cè)的功能,而真正去做解析的事情委托給了函數(shù)parseInternal
,正是這句代碼調(diào)用了我們自定義的解析函數(shù).
??在parseInternal
中并不是直接調(diào)用自定義的doParse
函數(shù),而是進(jìn)行了一系列的數(shù)據(jù)準(zhǔn)備,包括對(duì)beanClass
scope
lazyInit
等屬性的準(zhǔn)備.
@Override
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
String parentName = getParentName(element);
if (parentName != null) {
builder.getRawBeanDefinition().setParentName(parentName);
}
// 獲取自定義標(biāo)簽中的class,此時(shí)會(huì)調(diào)用自定義解析器如UserBeanDefinitionParser中的getBeanClass方法
Class<?> beanClass = getBeanClass(element);
if (beanClass != null) {
builder.getRawBeanDefinition().setBeanClass(beanClass);
}
else {
// 若子類沒(méi)有重寫getBeanClass方法則嘗試檢查子類是否重寫getBeanClassName方法
String beanClassName = getBeanClassName(element);
if (beanClassName != null) {
builder.getRawBeanDefinition().setBeanClassName(beanClassName);
}
}
builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
if (containingBd != null) {
// 若存在父類則使用父類的scope屬性
// Inner bean definition must receive same scope as containing bean.
builder.setScope(containingBd.getScope());
}
if (parserContext.isDefaultLazyInit()) {
// 調(diào)用子類重寫的doParse方法進(jìn)行解析
// Default-lazy-init applies to custom bean definitions as well.
builder.setLazyInit(true);
}
doParse(element, parserContext, builder);
return builder.getBeanDefinition();
}
??回顧一下全部的自定義標(biāo)簽處理過(guò)程,雖然在實(shí)例中我們定義
UserBeanDefinitionParser
,但是在其中我們只是做了與自己業(yè)務(wù)邏輯相關(guān)的部分.不過(guò)我們沒(méi)做但是并不代表沒(méi)有,在這個(gè)處理過(guò)程中同樣也是按照Spring
中默認(rèn)標(biāo)簽的處理方式進(jìn)行,包括創(chuàng)建BeanDefinition
以及進(jìn)行相應(yīng)默認(rèn)屬性的設(shè)置,對(duì)于這些工作Spring
都默默地幫我們實(shí)現(xiàn)了,只是暴露出一些接口來(lái)供用戶實(shí)現(xiàn)個(gè)性化的業(yè)務(wù).通過(guò)對(duì)本章的了解,相信讀者對(duì)Spring
中自定義標(biāo)簽的使用以及在解析自定義標(biāo)簽過(guò)程中Spring
為我們做了哪些工作會(huì)有一個(gè)全面的了解,到此為止我們已經(jīng)完成了Spring
中全部的解析工作,也就是說(shuō)到現(xiàn)在為止我們已經(jīng)理解了Spring
將bean
從配置文件到加載到內(nèi)存中的全過(guò)程,而接下來(lái)的任務(wù)便是如何使用這些bean
.