Spring 源碼(4)在Spring配置文件中自定義標籤如何實現?

Spring 配置文件自定義標籤的前置條件

在上一篇文章//www.cnblogs.com/redwinter/p/16165274.html Spring BeanFactory的創建過程中了解了BeanDefinition的載入和BeanFactory的創建,並且提到了Spring留了一個擴展點就是用戶可以自定義標籤進行解析BeanDefinition。

基於Spring源碼在處理訂製的標籤時是通過訂製的命名空間處理器和xsd文件進行解析的,在spring的classpath下的META-INF/spring.schemas和META-INF/spring.handlers,並且需要將標籤的解析器註冊到BeanDefinition的解析器中,這樣說起來比較抽象,接下來我們自己定義一個標籤就明了了。

定義標籤屬性類

創建一個需要解析的標籤的屬性,比如在Spring配置文件中經常看到的<context:component-scan base-package="com.redwinter.test"/> ,這裡的component-scan就是標籤屬性。

/**
 * @author <a href="//www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class Redwinter {

	private String username;
	private String email;
	private String password;

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
}

定義一個Redwinter類,裡面三個屬性,當然你可以自己定義你需要的屬性,我這裡就隨便寫啦。

定義標籤屬性解析器類

定義好標籤的屬性之後就需要定義一個解析器對這些屬性進行解析,定義解析器需要繼承AbstractSingleBeanDefinitionParser,這個類是實現了BeanDefinitionParser的類,他下面有很多實現類,一般來說我們的Bean都是單例的,那就繼承AbstractSingleBeanDefinitionParser即可。

/**
 * @author <a href="//www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class RedwinterBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

	@Override
	protected Class<?> getBeanClass(Element element) {
		return Redwinter.class;
	}

	@Override
	protected void doParse(Element element, BeanDefinitionBuilder builder) {
		/**
		 * 自定義解析xml的自定義欄位,並做相應的其他處理
		 */
		String username = element.getAttribute("username");
		String email = element.getAttribute("email");
		String password = element.getAttribute("password");
		if (StringUtils.hasText(username)){
			builder.addPropertyValue("username",username);
		}
		if (StringUtils.hasText(email)){
			builder.addPropertyValue("email",email);
		}
		if (StringUtils.hasText(password)){
			builder.addPropertyValue("password",password);
		}
	}
}

這個解析器主要是重寫了父類的兩個方法,一個是getBeanClass用於返回對應的標籤屬性類,一個是解析屬性doParser,這裡我只是從element中獲取出來然後進行了下判斷在加入到屬性值中,當然這裡你可以自定義自己的邏輯處理。

定義命名空間處理器類

定義命名空間處理器需要繼承NamespaceHandlerSupport,然後重寫他的init方法,將解析器註冊進去,這個解析器就是上面定義的用來解析標籤屬性的解析器。

/**
 * @author <a href="//www.cnblogs.com/redwinter/">redwinter</a>
 * @since 1.0
 **/
public class RedwinterNameSpaceHandler extends NamespaceHandlerSupport {
	@Override
	public void init() {
		// 這裡的屬性必須和xsd中指定的屬性一致,否則報錯
		//org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Cannot locate BeanDefinitionParser for element [dl]
		registerBeanDefinitionParser("dl",new RedwinterBeanDefinitionParser());
	}
}

這裡需要注意的是,進行註冊時需要指定一個elementName,這個值必須和xml中定義的名稱一致,否者的話就會報如下錯:

org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Cannot locate BeanDefinitionParser for element [dl]

我這裡定義的元素名稱叫dl。

定義xsd文件

xsd文件就是spring進行xml解析時解析的標籤,當然你可以定義dtd文件,不過現在一般都用xsd文件,我這裡命名為redwinter.xsd,完整文件如下:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>

<xsd:schema xmlns="//www.redwinter.com/schema/redwinter"
			xmlns:xsd="//www.w3.org/2001/XMLSchema"
			targetNamespace="//www.redwinter.com/schema/redwinter"
			elementFormDefault="qualified"
			attributeFormDefault="unqualified">
	<xsd:element name="dl">
		<xsd:complexType>
			<xsd:attribute name="id" type="xsd:string"/>
			<xsd:attribute name="username" type="xsd:string" use="required"/>
			<xsd:attribute name="email" type="xsd:string" use="required"/>
			<xsd:attribute name="password" type="xsd:string" use="required"/>
		</xsd:complexType>
	</xsd:element>
</xsd:schema>

這裡有幾個點需要注意: schema標籤下有個targetNamespace,這裡指定了命名空間叫//www.redwinter.com/schema/redwinter ,那麼在進行spring配置文件的時候引入的namespace就是這個,然後有個name="dl",這裡的這個dl就是處理器中定義的元素名稱,而且必須一致,不然解析不到,下面定義的屬性就是標籤屬性類中定義的刷新,這個id是表示唯一的Bean名稱。

編寫spring.schemas和spring.handlers文件

這裡直接列出完整文件內容:

  • spring.schemas文件
http\://www.redwinter.com/schema/redwinter.xsd=META-INF/redwinter.xsd

這裡需要注意的是,這裡配置的key也是需要在spring配置文件中引入的,value就是上一步定義的xsd文件所在路徑

  • spring.handlers文件
http\://www.redwinter.com/schema/redwinter=com.redwinter.test.RedwinterNameSpaceHandler

這裡配置的key就是上一步定義的xsd文件中定義的targetNamespace,value就是定義的命名空間處理器。

到這裡自定義標籤和解析就完成了,最後就需要在spring配置文件中引入並配置。

驗證自定義屬性標籤

我這裡定義個角spring-test.xml的文件進行配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
	   xmlns:xsi="//www.w3.org/2001/XMLSchema-instance" xmlns:mvc="//www.springframework.org/schema/mvc"
	   xmlns:context="//www.springframework.org/schema/context"
	   xmlns:redwinter="//www.redwinter.com/schema/redwinter"
	   xsi:schemaLocation="//www.springframework.org/schema/mvc //www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
		//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans-3.1.xsd
		//www.springframework.org/schema/context //www.springframework.org/schema/context/spring-context.xsd
		//www.redwinter.com/schema/redwinter  //www.redwinter.com/schema/redwinter.xsd
		">
	<!--自定義標籤-->
	<redwinter:dl id ="redwinter" email="[email protected]" password="123456" username="冬玲記憶"/>
	<redwinter:dl id ="redwinter123456"  email="[email protected]" password="123456" username="冬玲記憶"/>

</beans>

驗證是否配置正確:

public class BeanCreate {

	@Test
	public void classPathXml() {
//		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-test.xml");
		ClassPathXmlApplicationContext context = new MyClassPathXmlApplicationContext("classpath:spring-test.xml");
      
		Redwinter redwinter = (Redwinter) context.getBean("redwinter");
		System.out.println(redwinter.getEmail());

		Redwinter redwinter123456 = (Redwinter) context.getBean("redwinter123456");
		System.out.println(redwinter123456.getEmail());
	}
}

輸出:

[email protected]
[email protected]

那說明自定義標籤生效了,並且成功解析出來。

接下來就是繼續介紹Spring 容器的實現AbstractApplicationContex#refresh的第三個方法,這個方法其實就是BeanFactory使用的前戲準備,而第一個方法是BeanFactory刷新的前戲準備。