1. 程式人生 > >Spring自定義XML標籤解析及其原理分析

Spring自定義XML標籤解析及其原理分析

一、自定義XML標籤

先新建一個類

public class User {
    private String userName;
    private String email;
    ...省略setter、getter
}

新建一個BeanDefinitionParser

public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
    @Override
    protected Class<?> getBeanClass(Element element) {
        return
User.class; } @Override protected void doParse(Element element, BeanDefinitionBuilder builder) { String name = element.getAttribute("userName"); String email = element.getAttribute("email"); if (StringUtils.hasText(name)) builder.addPropertyValue("userName"
, name); if (StringUtils.hasText(email)) builder.addPropertyValue("email", email); } }

新建一個NameSpaceHandler

public class UserNameSpaceHandler extends NamespaceHandlerSupport {
   @Override
    public void init() {
        registerBeanDefinitionParser("user",new UserBeanDefinitionParser());
    }
}

在resources/META-INF目錄下建一個spring-test.xsd檔案(用於XML檢驗)

<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
      targetNamespace="http://www.season.com/schema/user"
      xmlns:tns="http://www.season.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>

在resources/META-INF目錄下新建spring.handlers檔案寫上

http\://www.season.com/schema/user=com.season.meta.UserNameSpaceHandler

在resources/META-INF目錄下新建spring.schemas檔案寫上

http://www.season.com/schema/user.xsd=META-INF/spring-test.xsd

在resources目錄下建一個beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:myname="http://www.season.com/schema/user"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.season.com/schema/user
       http://www.season.com/schema/user.xsd">

    <myname:user id="user" email="[email protected]" userName="season"/>
</beans>

測試:

@Test
public void testNameSpaceHandler(){
    ApplicationContext actx = new ClassPathXmlApplicationContext("beans.xml");
    User user = (User) actx.getBean("user");
    System.out.println(user);
}

二、原理分析

取XML配置的是XmlBeanDefinitionReader,它會先用SAX技術把XML配置檔案讀取成一個Document物件,然後利用BeanDefinitionDocumentReader對Document物件的節點進行解析,最後把bean封裝成BeanDefinition儲存在BeanRegistry中(參見:Spring IOC實現原理筆記(二) – 載入XML配置),如下:

XmlBeanDefinitionReader類

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    //實際建立的是DefaultBeanDefinitionDocumentReader類
     BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
     int countBefore = this.getRegistry().getBeanDefinitionCount();
    //【標記1】用BeanDefinitionDocumentReader對Document物件的節點進行解析
     documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
     return this.getRegistry().getBeanDefinitionCount() - countBefore;
}

上面【標記1】出就是解析節點的開始,節點分預設節點和自定義節點兩種情況。其中解析自定義節點的原始碼如下:

DefaultBeanDefinitionDocumentReader類


    //解析自定義節點
    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        String namespaceUri = this.getNamespaceURI(ele);
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if(handler == null) {
            ...
            return null;
        } else {
            return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
        }
    }

上面程式碼中,首先會拿到節點的中的名稱空間URI,(在上面的例子中是:http://www.season.com/schema/user),接著從NamespaceHandlerResolver中拿到對應該名稱空間URI解析的NamespaceHandler,然後呼叫NamespaceHandler的parse進行解析。

關鍵程式碼是this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri),其中readerContext是上面【標記1】處的createReaderContext(resource)創建出來的,如下:

XmlBeanDefinitionReader類

public XmlReaderContext createReaderContext(Resource resource) {
    return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor,
this, this.getNamespaceHandlerResolver());
}

//上面呼叫了getNamespaceHandlerResolver方法
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
    if(this.namespaceHandlerResolver == null) {
        this.namespaceHandlerResolver = this.createDefaultNamespaceHandlerResolver();
    }
    return this.namespaceHandlerResolver;
}

protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
    return new DefaultNamespaceHandlerResolver(this.getResourceLoader().getClassLoader());
}

上面可以看到getNamespaceHandlerResolver()獲取到的是DefaultNamespaceHandlerResolver例項,所以接下來去看該類

public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver {
    ...
    //【標記2】
    public DefaultNamespaceHandlerResolver(ClassLoader classLoader) {
        this(classLoader, "META-INF/spring.handlers");
    }

    public NamespaceHandler resolve(String namespaceUri) {
        Map<String, Object> handlerMappings = this.getHandlerMappings();
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if(handlerOrClassName == null) {
            return null;
        } else if(handlerOrClassName instanceof NamespaceHandler) {
            return (NamespaceHandler)handlerOrClassName;
        } else {
            String className = (String)handlerOrClassName;
            ...
               Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
               ...
              //【標記3】
               NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);
               //【標記4】
               namespaceHandler.init();
               handlerMappings.put(namespaceUri, namespaceHandler);
               return namespaceHandler;
            ...
        }
    }

    private Map<String, Object> getHandlerMappings() {
        if(this.handlerMappings == null) {
            synchronized(this) {
                if(this.handlerMappings == null) {
                    try {
                        //【標記4】
                        Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                        ...
                        Map<String, Object> handlerMappings = new ConcurrentHashMap(mappings.size());
                        CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                        this.handlerMappings = handlerMappings;
                    } catch...
                }
            }
        }
        return this.handlerMappings;
    }
    ...
}

DefaultNamespaceHandlerResolver是會把所有NamespaceHandler快取到handlerMappings(ConcurrentHashMap例項)裡,resolve方法就是從handlerMappings獲取相應的NamespaceHandler返回出去。

說說上面的標記的地方:
【標記2】:Spring預設的NamespaceHandler配置檔案路徑為META-INF/spring.handlers
【標記3】:未初始化的NamespaceHandler是通過反射例項出來的
【標記4】:例項化出NamespaceHandler後,第一件事是呼叫其init方法
【標記5】:載入META-INF/spring.handlers配置檔案,並記錄到handlerMappings。

總結

Spring在解析XML的時候,解析到是自定義節點,就會找相對應的NameSpaceHandler去解析;而Spring預設載入寫在META-INF/spring.handlers配置檔案中配置的NameSpaceHandler,通過反射將其例項化

2、NameSpaceHandler解析節點

那NameSpaceHandler如何解析的呢?以上面的例子來分析。首先解析自定義節點的原始碼中,Spring拿到相應的NameSpaceHandler後,會呼叫器parse方法,而上面自定義的UserNameSpaceHandler沒看到有parse方法,那就去父類,如下:

NamespaceHandlerSupport類

public abstract class NamespaceHandlerSupport implements NamespaceHandler {
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        return this.findParserForElement(element, parserContext).parse(element, parserContext);
    }

    private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        //拿到標籤名稱(對應上面例子的 user)
        String localName = parserContext.getDelegate().getLocalName(element);
        BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName);
        ...
        return parser;
    }

    protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
        this.parsers.put(elementName, parser);
    }
    ...
}

上面看到parse方法呼叫了findParserForElement拿到BeanDefinitionParser,再調BeanDefinitionParser的parse方法進行解析。要拿到BeanDefinitionParser看這句程式碼this.parsers.get(localName),其中parsers是一個Map,鍵存標籤名稱,值為相對應的BeanDefinitionParser。那我們自定義的BeanDefinitionParser(對應上面的UserBeanDefinitionParser)什麼時候放進這個Map中呢?

回顧下【標記4】處程式碼,發現例項化出NamespaceHandler後,第一件事是呼叫其init方法,我們自定義的UserNameSpaceHandler在init方法中呼叫了registerBeanDefinitionParser方法,該方法在父類中,做的事情就是把我們傳入的鍵值對存入parsers這個Map中,所以可以被找到並取出來進行解析節點的操作了。

相關推薦

Spring定義XML標籤解析及其原理分析

一、自定義XML標籤 先新建一個類 public class User { private String userName; private String email; ...省略setter、getter } 新建一個Bean

Spring原始碼閱讀筆記05:定義xml標籤解析

  在上篇文章中,提到了在Spring中存在預設標籤與自定義標籤兩種,並且詳細分析了預設標籤的解析,本文就來分析自定義標籤的解析,像Spring中的AOP就是通過自定義標籤來進行配置的,這裡也是為後面學習AOP原理打下基礎。   這裡先回顧一下,當Spring完成了從配置檔案到Document的轉換並提取對應

Spring 定義 XML 配置擴充套件

XML 配置用的越來越少了,不過還是有比較瞭解一下 Spring 對 XML 的擴充套件機制。Spring 是基於 Dom 進行操作的。 可以先看看官方文件的介紹: 9.2. XML Schema Authoring 9.2.1. Introduction Since

Android開發進階——定義View的使用及其原理探索

  在Android開發中,系統提供給我們的UI控制元件是有限的,當我們需要使用一些特殊的控制元件的時候,只靠系統提供的控制元件,可能無法達到我們想要的效果,這時,就需要我們自定義一些控制元件,來完成我們想要的效果了。下面,我就來講講自定義控制元件的那些事。   首先,我來講講Android的控制元件架構。

Spring定義標籤解析

一:專案demo Spring提供了可擴充套件的支援,利用如下的程式碼來一步步實現spring自定義解析。 專案結構如下,引入spring4.0 1.建立一個user物件 public class User { private String user

6.1 如何在spring定義xml標簽

關聯 tex and 啟動流程 調用 mls ram 如果 .com dubbo自定義了很多xml標簽,例如<dubbo:application>,那麽這些自定義標簽是怎麽與spring結合起來的呢?我們先看一個簡單的例子。 一 編寫模型類 1 packa

定義xml spring bean

位置 handle schema object 解析 spring def ati 註解 一. xml中bean解析過程 掃描META-INF下面的 spring.schemas bean定義對應的xsd位置,在IDEA中可以輔助校驗) spring.handl

spring 定義標籤的實現

在我們進行Spring 框架開發中,估計用到最多的就是bean 標籤吧,其實在Spring中像<mvc/><context/>這類標籤以及在dubbo配置的標籤都是屬於自定義的標籤,標籤的解析,已經由作者就行了解析,我們用就好了,那麼我們今天就進行開發一個自己的標籤,模擬 &

spring 定義標籤實現

Spring 自定義標籤實現 一、自定義Spring標籤簡介 Spring官方文件 42.1中,介紹瞭如何自定義Spring標籤,步驟如下: 1、編寫一個XML Schema描述您的自定義元素 2、編寫自定義名稱空間處理程式實現,實現NamespackHand

Spring定義標籤實現及踩過的坑(親測)

專案結構 先來一張專案結構圖,因為LZ是用的IDEAL,網上的大部分都是用的eclipse來實現: 這裡也大致說一下專案的新建,考慮到有的讀者會想LZ一樣對IDEAL的使用不是很熟練。 新建一個spring專案(不會的話網上搜索一下,很簡單的),建好之後,再新建這些資料夾,點選File—>Pr

Spring定義標籤spring.handlers的載入過程

版權宣告:------------轉載請標明連結.部落格內容僅供參考,一切以官方文件為準!------------    https://blog.csdn.net/wabiaozia/article/details/78631259 此篇部落格分為三部分:1 sch

spring 定義解析

設計配置屬性和JavaBean 編寫XSD檔案 編寫NamespaceHandler和BeanDefinitionParser完成解析工作 編寫spring.handlers和spring.schemas串聯起所有部件 在Bean檔案中應用     會用到N

Spring boot中定義Json引數解析

轉載請註明出處。。。 一、介紹 用過springMVC/spring boot的都清楚,在controller層接受引數,常用的都是兩種接受方式,如下 1 /** 2 * 請求路徑 http://127.0.0.1:8080/test 提交型別為application/json 3

原始碼分析Elastic-Job前置篇:Spring定義名稱空間原理

在Spring中使用Elastic-Job的示例如下: <!--配置作業註冊中心 --> <reg:zookeeper id="regCenter" server-lists="${gis.dubbo.registry.address}"

定義xml解析框架

標籤: 我們在工作中,經常會從伺服器獲取資料並進行解析,伺服器返回的資料有兩種:json和xml。json我們可以用gson或者fastjson等優秀的開源框架去進行解析,省去不少麻煩,通常我們只需要把bean設計出來,然後呼叫一句話就可以解析成功了。而解析xml資料,

spring boot 定義請求引數解析註解

介紹 一些請求引數, 需要解析成某個自定義的類, 而spring boot中並沒有提供這樣自動轉換的註解, 但是,spring boot 預留了擴充套件介面,所以,我們可以自定義實現一個註解.

RobotFramework定義系統關鍵字解析txt,csv,dbf,xml格式文件(上)

證券 frame art 字符 之前 print framwork 搜索 import 一、需求 業務中有一個功能菜單,解析交易所的證券信息文件,導入到數據表中,有些問價會導入幾個數據表當中。 本次自動化設計的目標就是免去人工對比文件數據和數據表數據的過程; 二、已知

Spring定義屬性編輯器

tex java block tom support bar white sim name bean類: [html] view plain copy package com.zm.bean; import java.util.Date; pu

spring:定義限定符註解@interface, 首選bean

限定符 喜歡 class autowire bean interface 通過 自動 .... spring:自定義限定符註解@interface, 首選bean 1.首選bean 在聲明bean的時候,通過將其中一個可選的bean設置為首選(primary)bean能夠避

Docker定義網橋pipework工作原理

fault env 配置路由 tex 網卡 switch vswitch out 新的 自定義網橋1.創建一工作目錄通過brctl查看show命令啟用一個運行/bin/bash的容器,並指定--net=none再開啟一個新的終端,查找這個容器的進程ID,然後創建它的命名空間