自定義標籤的作用
有同學可能要問,我們常用的標籤,Spring中已經定義好了,幹嘛還要花這功夫去自定義標籤?
我們自定義標籤,可以對Spring進行擴展,因為Spring提供了一個可插拔的標準接口。Spring中很多功能都可以使用自定義標籤來擴展,比如Spring AOP 、dubbo 等等。
如果你想寫一個框架,瞭解如何自定義標籤和相應的原理是必須走的一步。
如何定義自定義標籤
自定義標籤可以分為以下步驟:
(1) 編寫schemas文件,告訴Sprign容器 我們自定義的xsd文件在哪裡
(2) 編寫.xsd文件,定義配置時我們可以使用哪些屬性
(3) 編寫spring.handlers,擴展Namespaceandler命名空間註冊器和定義解析器
(4) 在xml中使用自定義標籤
下面我們將通過一個例子來來實現:
整個的目錄情況如下:
1.編寫模型類
假設我們有一個類User,並且我們需要在xml文件中使用自定義標籤聲明該User對象,如下User定義:
<code>public
class
User {private
String
username;private
String
password;public
String
getUsername( ) {return
username; }public
void
setUsername(String
username ) {this
.username = username; }public
String
getPassword( ) {return
password; }public
void
setPassword(String
password ) {this
.password = password; } }/<code>
如下是我們使用自定義標籤在spring的xml中為其聲明對象的配置
<code><
beans
xmlns
="http://www.springframework.org/schema/beans"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xmlns:myuser
="http://www.wy.com/schema/user"
xsi:schemaLocation
="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.wy.com/schema/user http://www.wy.com/schema/user.xsd"
><
myuser:user
id
="user"
username
="wj"
password
="123456"
/>beans
>/<code>
這裡使用myuser:user 標籤聲明為user的bean,這裡的myuser對象上面的xmlns:myuser,其後對應一個連接 http://www.wy.com/schema/user,Spring在解析該鏈接時會到META-INF下的Spring.handlers和Spring.schemas文件。然後讀取這兩個文件:
Spring.handlers
<code>http\://www
.wy.com/schema/user=com.wy.anno.MyNameSpaceHandler/<code>
Spring.handlers指明瞭當前命名空間的處理類
Spring.schemas
<code>http\://www
.wy.com/schema/user.xsd=META-INF/user.xsd/<code>
Spring.schemas 指定了一個xsd文件,該文件中聲明瞭 myuser:user 各個屬性的定義
<code><
xsd:schema
xmlns
="http://www.wy.com/schema/user"
xmlns:xsd
="http://www.w3.org/2001/XMLSchema"
targetNamespace
="http://www.wy.com/schema/user"
elementFormDefault
="qualified"
><
xsd:complexType
name
="user"
><
xsd:attribute
name
="id"
type
="xsd:string"
><
xsd:annotation
><
xsd:documentation
>xsd:documentation
>xsd:annotation
>xsd:attribute
><
xsd:attribute
name
="username"
type
="xsd:string"
><
xsd:annotation
><
xsd:documentation
>xsd:documentation
>xsd:annotation
>xsd:attribute
><
xsd:attribute
name
="password"
type
="xsd:string"
><
xsd:annotation
><
xsd:documentation
>xsd:documentation
>xsd:annotation
>xsd:attribute
>xsd:complexType
><
xsd:element
name
="user"
type
="user"
><
xsd:annotation
><
xsd:documentation
>xsd:documentation
>xsd:annotation
>xsd:element
>xsd:schema
>/<code>
該xsd文件中聲明瞭三個屬性:id、username和password ,這裡設置 的屬性和我們的User對象中的屬性沒有直接的關係
接下來我們看一下Spring.handlers中定義的MyNameSpaceHandler聲明:
<code>public
class
MyNameSpaceHandler
extends
NamespaceHandlerSupport
{public
void
init
()
{ registerBeanDefinitionParser("user"
,new
UserBeanDefinitionParser()); } }/<code>
MyNameSpaceHandler註冊了user標籤的處理邏輯,真正的解析邏輯在 UserBeanDefinitionParser中。
<code>public
class
UserBeanDefinitionParser
extends
AbstractSingleBeanDefinitionParser
{protected
Class> getBeanClass(Element element) {return
User.
class
; }protected
void
doParse
(Element element, BeanDefinitionBuilder builder)
{ String username = element.getAttribute("username"
); String password = element.getAttribute("password"
);if
(StringUtils.hasText(username)) { builder.addPropertyValue("username"
, username); }if
(StringUtils.hasText(password)) { builder.addPropertyValue("password"
, password); } } }/<code>
上面的邏輯是,獲取自定義標籤中的username和password屬性,賦值到User對象中。
測試類:
<code> ApplicationContext applicationContext =new
ClassPathXmlApplicationContext("spring.xml"
); User apple = applicationContext.getBean(User.
class
); System.out.println(apple.getUsername() +", "
+ apple.getPassword());/<code>
測試結果:
自定義標籤的解析原理
調用下面方法前的調用棧如下:
org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader
#parseBeanDefinitions
<code>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); } }/<code>
上述方法首先判斷是不是默認的命名空間,如果是,則按照默認的命名空間來 處理,如果不是,則按照自定義的命名空間進行處理。myuser:user 就屬於自定義命名空間。下面我們看下parseCustomElement方法
<code>public
BeanDefinition parseCustomElement(Element ele, 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)); }/<code>
1、首先獲取所有的命名空間URL
2、根據命名空間url後者myuser:user 的解析類
即獲取下面的類
調用 org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#resolve 方法
<code>public NamespaceHandler resolve(String
namespaceUri) {Map
<String
,Object
> handlerMappings = getHandlerMappings();Object
handlerOrClassName = handlerMappings.get(namespaceUri);if
(handlerOrClassName ==null
) {return
null
; }else
if
(handlerOrClassNameinstanceof
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"
); } NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); 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); } } }/<code>
1、獲取handlerMapping對象,key為當前的命名空間url,value為當前命令空間的處理對象。
默認取META-INF下的spring.handlers文件。
2、找到http://www.wy.com/schema/user對應的邏輯處理類 com.wy.anno.MyNameSpaceHandler類
3、調用 MyNameSpaceHandler的init 方法,註冊標籤解析類
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();//調用處理邏輯的處理 方法
4、我們繼續查看NamespaceHandler.parse方法
<code>public
BeanDefinitionparse
(Element element, ParserContext parserContext
) { BeanDefinitionParser parser = findParserForElement(element, parserContext);return
(parser !=null
? parser.parse(element, parserContext) :null
); }/<code>
這裡的parser就是我們定義的UserBeanDefinitionParser類。
<code>private
BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { 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; } /<code>
上述方法即從 Map parsers = new HashMap<>();緩存獲取解析類,
在registerBeanDefinitionParser("user", new UserBeanDefinitionParser());註冊時的key必須與xml文件中myuser:user後的user一致即可。
最後調用 UserBeanDefinitionParser的parser方法。
最後,解析流程圖如下: