Spring自定義標籤使用及原理

自定義標籤的作用

有同學可能要問,我們常用的標籤,Spring中已經定義好了,幹嘛還要花這功夫去自定義標籤?

我們自定義標籤,可以對Spring進行擴展,因為Spring提供了一個可插拔的標準接口。Spring中很多功能都可以使用自定義標籤來擴展,比如Spring AOP 、dubbo 等等。

如果你想寫一個框架,瞭解如何自定義標籤和相應的原理是必須走的一步。

如何定義自定義標籤

自定義標籤可以分為以下步驟:

(1) 編寫schemas文件,告訴Sprign容器 我們自定義的xsd文件在哪裡

(2) 編寫.xsd文件,定義配置時我們可以使用哪些屬性

(3) 編寫spring.handlers,擴展Namespaceandler命名空間註冊器和定義解析器

(4) 在xml中使用自定義標籤

下面我們將通過一個例子來來實現:

整個的目錄情況如下:


Spring自定義標籤使用及原理

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>

測試結果:

Spring自定義標籤使用及原理


自定義標籤的解析原理

調用下面方法前的調用棧如下:


Spring自定義標籤使用及原理

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 的解析類

即獲取下面的類


Spring自定義標籤使用及原理

調用 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

(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"

); } 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為當前命令空間的處理對象。

Spring自定義標籤使用及原理


Spring自定義標籤使用及原理

默認取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

BeanDefinition

parse

(

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>


Spring自定義標籤使用及原理

上述方法即從 Map parsers = new HashMap<>();緩存獲取解析類,

在registerBeanDefinitionParser("user", new UserBeanDefinitionParser());註冊時的key必須與xml文件中myuser:user後的user一致即可。

最後調用 UserBeanDefinitionParser的parser方法。

最後,解析流程圖如下:

Spring自定義標籤使用及原理


分享到:


相關文章: