基於昨天的那篇文章,我們今天來聊一個比較簡單輕鬆點的話題,今天我們來實現一個自定義的標籤。
先明確下整個項目的結構,網上有很多關於自定義標籤的實現方式,但是教程包結構不清晰,導致測試無法正常進行,博主自己也栽了一個坑,爲了讓朋友們可以快速驗證結果,我們首先來介紹一下包結構。
第一個工程包結構,該工程的作用是爲了實現一個自定義標籤。
第二個工程是爲了驗證自定義標籤是否能夠正常工作。
這裏說一下爲什麼創建了兩個工程,博主就是在這裏遇到了一個坑,最開始博主在一個工程下實現了標籤並且去做驗證,遇到了一個比較怪異的報錯,如下,大概意思就是說 'mytag:annotation' 找不到,苦思冥想命名定義了爲什麼會找不到呢?朋友們混個眼熟,後面會講爲啥。
Caused by: org.xml.sax.SAXParseException; lineNumber: 10; columnNumber: 42; cvc-complex-type.2.4.c: 通配符的匹配很全面, 但無法找到元素 'mytag:annotation' 的聲明。
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(ErrorHandlerWrapper.java:203)
at
清楚了包結構,我們先來介紹第一個工程涉及文件的作用
MyNamespaceDefinitionParse
:用於解析標籤的具體實現MyNamespaceHandler
:命名空間處理器,用於註冊標籤對應的解析器MyTagBean
:自定義BeanClass
spring.handlers
:指定使用的命名空間處理器,即指定MyNamespaceHandler
spring.schemas
:用於指定.xsd
文件位置的resource-1.0.xsd
:用於定義標籤,也可理解成規範標籤編寫規則的
我們先來介紹下
resource-1.0.xsd
文件內容。
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.zxz.com/resource"
targetNamespace="http://www.zxz.com/resource">
<!-- 定義一個類型,該類型內可以指定一個或多個標籤 -->
<xsd:complexType name="annotationType">
<!-- 定義屬性,名字叫id,可以理解成我們定義bean標籤的時候寫的 id 那個屬性 -->
<xsd:attribute name="id" type="xsd:ID">
<xsd:annotation>
<xsd:documentation><![CDATA[ The unique identifier for a bean. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<!-- 定義屬性,,名字叫name -->
<xsd:attribute name="name" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ The name of bean. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<!-- 定義屬性,名字叫myPackage -->
<xsd:attribute name="myPackage" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ The scan package. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<!-- 定義一個標籤,名字叫annotation,對應的類型是我們上面定義的annotationType類型 -->
<!-- 即annotation標籤包含了 id、name、myPackage 三個屬性 -->
<xsd:element name="annotation" type="annotationType">
<xsd:annotation>
<xsd:documentation><![CDATA[ The annotation config ]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:schema>
spring.schemas
文件內容如下,用於指定resource-1.0.xsd
文件的位置,左邊爲 key,右邊爲該文件在本地的位置,如果該文件在本地找不到會嘗試去遠程去找,即左邊 key 指定的遠程位置去獲取。
http\://www.zxz.com/resource/resource-1.0.xsd=/resource-1.0.xsd
spring.handlers
文件內容如下,用於指定自定義標籤的解析器位置,左邊爲 key,右邊爲該文件在本地的位置。
http\://www.zxz.com/resource=com.zxz.resource.MyNamespaceHandler
MyTagBean.java
文件,設置了name
myPackage
兩個屬性,注意這個字段名和resource-1.0.xsd
文件定義的名字實際上沒有什麼直接聯繫,只是名字相同而已。
package com.zxz.resource;
/**
* @author zxz
*/
public class MyTagBean {
private String name;
private String myPackage;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMyPackage() {
return myPackage;
}
public void setMyPackage(String myPackage) {
this.myPackage = myPackage;
}
@Override
public String toString() {
return "MyTagBean{" + "name='" + name + '\'' + ", myPackage='" + myPackage + '\'' + '}';
}
}
MyNamespaceHandler.java
文件內容如下,其繼承了NamespaceHandlerSupport
,實現了init
方法。用於註冊具體annotation
標籤對應的解析器,這個和上一講的<context:component-scan>
的做法是一樣的,不熟悉的朋友可以先看上一講。
package com.zxz.resource;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* @author zxz
*/
public class MyNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("annotation", new MyNamespaceDefinitionParse());
}
}
MyNamespaceDefinitionParse.java
內容如下,其實現了BeanDefinitionParse
接口,來實現parse
方法。
package com.zxz.resource;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
/**
* @author zxz
*/
public class MyNamespaceDefinitionParse implements BeanDefinitionParser {
public BeanDefinition parse(Element element, ParserContext parserContext) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
// 設置beanClass
beanDefinition.setBeanClass(MyTagBean.class);
MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues();
// 添加name屬性
if (element.hasAttribute("name")) {
mutablePropertyValues.addPropertyValue("name", element.getAttribute("name"));
}
// 添加package屬性
if (element.hasAttribute("myPackage")) {
mutablePropertyValues.addPropertyValue("myPackage", element.getAttribute("myPackage"));
}
String id = element.getAttribute("id");
// 拿到註冊表, 註冊BeanDefinition
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
return beanDefinition;
}
}
至此,我們的自定義標籤就製作完成了,接下來打成 jar 包,在第二個測試工程中進行測試。
第二個工程文件介紹。
applicationContext.xml
:編寫定義標籤內容Test.java
:測試方法pom.xml
:引入第一個工程的 jar 包
applicationContext.xml
文件內容如下,在測試啓動的時候會找http://www.zxz.com/resource/resource-1.0.xsd
和http://www.zxz.com/resource
對應的.xsd
文件和對應的處理器。回答下前面的那個報錯原因,因爲在 spring 啓動的時候會先掃描本地所有的.handlers
和.schemas
文件,如果本地找不到回去遠程找。但是從我們的報錯可以看出,應該是沒有掃描到我們自定義的兩個文件,但是我們將其打成 jar 包,在另外一個工程裏引用就可以掃到,所以我猜想 spring 啓動只掃描在 pom 文件指定的那些包下文件。
<?xml version="1.0" encoding="UTF-8"?>
<!-- xmlns:mytag="http://www.zxz.com/resource" 的標籤名,可以理解成指定一級標籤名,和 '<context:component-scan>' 的context一樣 -->
<beans xmlns:mytag="http://www.zxz.com/resource"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.zxz.com/resource
http://www.zxz.com/resource/resource-1.0.xsd"
<!-- http://www.zxz.com/resource 是用於指定handler類的位置 -->
<!-- http://www.zxz.com/resource/resource-1.0.xsd 用於指定 .xsd文件的位置 -->
<!-- 自定義標籤 -->
<mytag:annotation id="myTagBean" name="zxz" myPackage="com.zxz.demo"/>
</beans>
Test.xml
文件內容如下。
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
System.out.println(context.getBean("myTagBean"));
}
}
11. 運行測試代碼,最終輸出結果如下。
總結
以上即爲創建一個自定義標籤的流程,全部代碼都經過測試並得出預期結果。具體的運行原理我在上一篇講解<context:component-scan>
標籤的源碼解析講解過了,不熟悉的朋友先看看上一篇內容。至此,我們關於標籤解析成BeanDefinition
對象並將其註冊到BeanFactory
的全部流程就講完了,涉及到的其他細節朋友大家自行了解,或者可以和我交流。下一篇文章我們來講講下一個比較重要的流程invokeBeanFactoryPostProcessors
,希望大家和我繼續堅持下去,搞定 Spring 最核心的靈魂。
歡迎關注我,共同學習