自定義標籤與解析 - Spring解密

2020-12-22 Java程式設計師之家

Spring

是一個開源的設計層面框架,解決了業務邏輯層和其他各層的鬆耦合問題,將面向接口的編程思想貫穿整個系統應用,同時它也是 Java工作中必備技能之一...

在 上一節 Spring解密 - 默認標籤的解析 中,重點分析了 Spring 對 默認標籤是如何解析的,那麼本章繼續講解標籤解析,著重講述如何對 自定義標籤進行解析。自定義標籤

在講解 自定義標籤解析 之前,先看下如何自定義標籤

定義 XSD 文件

定義一個 XSD 文件描述組件內容

<?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:applicationid="battcn"/>定義元素屬性: 元素屬性就是 attribute 標籤,我們聲明了一個必填的 name 的屬性,使用時 <battcn:applicationid="battcn"name="Levin"/>定義解析規則

1.創建一個類實現 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); }}這裡創建了一個 ApplicationBeanDefinitionParser 繼承 AbstractSingleBeanDefinitionParser(是:BeanDefinitionParser的子類), 重點就是重寫的 doParse,在這個裡面解析 XML 標籤的,然後將解析出的 value(Levin) 通過構造器方式注入進去

2.創建一個類繼承 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.BattcnNamespaceHandlerspring.schemas

http\://www.battcn.com/schema/battcn.xsd=battcn.xsd4.使用自定義標籤

申明 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>創建一個測試類,如果看到控制臺輸出了 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)); }}與默認標籤解析規則一樣的是,都是通過 getNamespaceURI(Nodenode) 來獲取命名空間,那麼 this.readerContext.getNamespaceHandlerResolver() 是從哪裡獲取的呢?我們跟蹤下代碼,可以發現在項目啟動的時候,會在 XmlBeanDefinitionReader 將所有的 META-INF/spring.handles 文件內容解析,存儲在 handlerMappers(一個ConcurrentHashMap) 中,在調用 resolve(namespaceUri) 校驗的時候在將緩存的內容提取出來做對比

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"); } // 根據命名空間尋找對應的信息 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 就已經被初始化為 了,而 BattcnNamespaceHandler 也調用了 init() 方法完成了初始化的工作。因此就接著執行這句代碼: handler.parse(ele,newParserContext(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 實例,並調用其自身的 doParse 方法進行進一步解析。最後就跟解析默認標籤的套路一樣了...

總結

熬過幾個無人知曉的秋冬春夏,撐過去一切都會順著你想要的方向走...

相關焦點

  • Spring-Task源碼解析
    其beanClass為org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler。scheduled-tasks其解析的源碼較長,在此不再貼出,解析之後形成的BeanDefinition結構如下圖:taskScheduler屬性即指向task:scheduler標籤,如果沒有配置,此屬性不存在。
  • Spring自定義轉換類,讓@Value更方便
    Spring為大家內置了不少開箱即用的轉換類,如字符串轉數字、字符串轉時間等,但有時候需要使用自定義的屬性,則需要自定義轉換類了。但這樣做有一些問題:無法做配置檢驗,不管是否配置錯誤,String類型的屬性都是可以讀取的;3 自定義轉換類使用自定義轉換類是更方便和安全的做法。我們來看看怎麼實現。
  • PHP編程實例:自定義函數實現簡單數字加密和解密算法
    加密和解密一般用於電子商務,但是一般的網站開發中也會用涉及到到加密和解密,特別是文件處理上,今天為大家講解一個自定義函數簡單的數字加密/解密算法實例。3、定義加密數字和解密數字的函數。4、調用自定義函數處理用戶輸入的數據,輸出加密數字和解密數字。
  • OneNote重磅功能:自定義標籤
    IT之家12月18日消息 微軟OneNote官方博客今天宣布,在未來幾周內為用戶提供自定義標籤功能。藉助此功能,用戶可以為代碼命名並選擇圖標來創建自定義代碼,該代碼將跨設備同步並顯示在應用搜索結果中。向頁面添加自定義標籤,並以任何方式保持井井有條,幫助你實現更多目標。
  • 在Lightning Aura組件中使用自定義標籤
    要訪問Aura組件中的自定義標籤,請使用 $Label global value provider。在這篇文章中,我們將看到如何在閃電光環組件中使用自定義標籤。創建自定義標籤轉到設置-創建-自定義標籤。單擊新的自定義標籤。輸入值作為名稱,值和描述。
  • Spring事務管理
    大大降低了我們的學習、使用成本1.TransactionDefinitionTransactionDefinition:事務定義。Spring事務管理框架將為我們管理事務,但不清楚該如何替我們管理,我們就通過事務定義來定義我們需要的事務管理信息,把這些信給事務管理器,它就知道我們的意圖了。1.1  來看看它都定義了哪些信息項:
  • 用戶標籤畫像系統,該如何支持創建靈活的自定義標籤?
    一、什麼是自定義標籤 首先,什麼是自定義標籤呢?給大家截個業界某公司的自定義標籤創建的圖: 二、自定義標籤的分類 自定義標籤都分為哪幾種類型呢?從大的創建邏輯上,可以細分為兩類: 1)配置類自定義標籤 所謂的配置類自定義標籤,是用戶可以通過點擊、交互等各種方式,實現自定義標籤的配置。
  • Springcloud序之Springboot2x模塊化+rest assured+AES加解密實現
    1、主模塊1.1、模塊結構從主模塊可以看出,我們這次的項目主要包括兩個子模塊,分別是web和aestest兩個模塊,兩個模塊文件夾裡面為各自的項目文件,且主模塊中未定義其他包和類。2、加解密模塊2.1、模塊結構加解密模塊主要有兩個包,aesutil和exception包,其中aesutil才是我們這個模塊的主角,exception包只是一個自定義的異常類而已
  • 黑馬程式設計師:案例—實現一個傳統自定義標籤
    今天學習目標:快速學習傳統自定義標籤的開發,接下來,將演示如何開發一個顯示IP位址的自定義標籤<itcast:ipTag/>,具體步驟如下:(1)編寫完成標籤功能的標籤處理器類。在chapter09工程中創建的一個標籤處理器類cn.itcast.chapter09.tag.IpTag。該類繼承了Tag接口的實現類TagSupport,具體如圖所示:(2)編寫TLD文件。
  • ...OAuth2 自定義異常響應|spring|override|oauth|request|cloud...
    自定義異常響應 無效 token 異常類重寫  新增 AuthExceptionEntryPoint.java  @Componentpublic class AuthExceptionEntryPoint implements AuthenticationEntryPoint{  @Overridepublic void
  • 快速創建 Spring Cloud 應用的 Spring Initializr 使用及原理
    實現過程中,可以通過 ProjectGenerationContext 獲取相關依賴,然後通過自定義邏輯完成文件生成工作。MainCompilationUnitCustomizer:自定義 MainApplication 編譯單元。MainSourceCodeCustomizer:自定義 MainApplication 源碼。
  • 安全的Spring Cloud配置
    本文涵蓋的主題是:敏感數據的加密和解密在伺服器端設置SSL配置客戶端上的SSL連接1.加密和解密如果使用JDK 8或更低版本,則首先需要下載並安裝Oracle提供的Java密碼學擴展(JCE)。它由兩個JAR文件(local_policy.jar和US_export_policy.jar)組成,需要覆蓋JRE lib/security目錄中的現有策略文件。
  • 如何記憶 Spring Bean 的生命周期
    若實現 InitializingBean 接口,調用 afterPropertiesSet() 方法 // 6.初始化後置處理 default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; }}常用場景有:對於標記接口的實現類,進
  • Edge 86穩定版發布:可自定義新標籤頁壁紙
    但對於普通用戶來說,最值得關注的改進可能是新標籤頁面,新版本中允許用戶自定義圖片作為新標籤頁的壁紙。想要為新標籤頁自定義壁紙,操作步驟如下1.在 Edge 86 穩定版中打開新標籤頁2.單擊右上角的設置圖標,訪問頁面布局3.選擇「自定義」,然後在背景中選擇本地圖像
  • spring和spring boot常用註解及使用
    @Service服務層組件,用於標註業務層組件,表示定義一個bean,自動根據bean的類名實例化一個首寫字母為小寫的bean,也可以指定bean的名稱:@Service(「beanName」)。4.不同點:@Resource是Java自己的註解,@Resource有兩個屬性重要屬性,分是name和type;Spring將@Resource註解的name屬性解析為bean的名字,而type屬性則解析為bean的類型。
  • 重學Java 設計模式:實戰代理模式「模擬mybatis-spring中定義DAO...
    另外像我們常用的MyBatis,基本是定義接口但是不需要寫實現類,就可以對xml或者自定義註解裡的sql語句進行增刪改查操作。解析下來我們會通過實現一個這樣的代理類交給spring管理的核心過程,來講述代理類模式。這樣的案例場景在實際的業務開發中其實不多,因為這是將這種思想運用在中間件開發上,而很多小夥伴經常是做業務開發,所以對Spring的bean定義以及註冊和對代理以及反射調用的知識了解的相對較少。但可以通過本章節作為一個入門學習,逐步了解。
  • 社交媒體登錄Spring Social的源碼解析
    本文就通過對Spring Social源碼進行一下解析,從而在我們後續開發第三方媒體平臺的登錄認證功能時,能更加的清晰。一、Spring Social結構化角度解析源碼Spring Social是一個幫助我們連接社交媒體平臺,方便在我們自己的應用上開發第三方登錄認證等功能的Spring 類庫。其中比較核心的類和接口,如下圖所示,我們來一一解析。
  • 面試官:你了解spring嗎?spring的兩大核心是什麼?
    IOC的實現原理在初始化一個Spring容器時,Spring會去解析指定的xml文件,當解析到其中的<bean>標籤時,會根據該標籤中的class屬性指定的類的全路徑名,通過反射創建該類的對象,並將該對象存入內置的Map中管理。其中鍵就是該標籤的id值,值就是該對象。
  • Spring 面試題及答案解析(7)
    這一篇說一些Java最流行的框架spring的一些知識點。大家注意到了沒有,我不說面試題,改用知識點了。因為後臺有小夥伴留言說有很多人不準備面試,可不可以發一些學習的知識點。prototype:    每次通過Spring容器獲取prototype定義的bean時,容器都將創建一個新的Bean實例,每個Bean實例都有自己的屬性和狀態,而singleton全局只有一個對象。
  • springboot+springsecurity實現前後端分離簡單實現!
    內容有點多,每一步都有詳細解析,請耐心看完,看不懂可以多看幾遍。。簡單理解: 自定義配置登錄成功、登陸失敗、註銷成功目標結果類,並將其注入到springsecurity的配置文件中。SelfAuthenticationProvider,該類是 AuthenticationProvider 的實現類:你可以在該類的 Authentication authenticate(Authentication authentication)自定義認證邏輯