1. 程式人生 > 其它 >Spring原始碼學習-自定義標籤實踐及原理

Spring原始碼學習-自定義標籤實踐及原理

Spring-Framework下新建Module

先看下工程結構

程式碼

Teacher

package com.fy.test.model;

public class Teacher {
	private Student student;

	public Teacher(Student student) {
		this.student = student;
	}

	public Student getStudent() {
		return student;
	}

	public void setStudent(Student student) {
		this.student = student;
	}

	@Override
	public String toString() {
		return "Teacher{" +
				"Student=" + student +
				'}';
	}
}


Student

package com.fy.test.model;

public class Student {
	private String name;
	private Integer age;
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "Student{" +
				"name='" + name + '\'' +
				", age=" + age +
				'}';
	}
}

開始自定義

1. 先定義自己的標籤fy.xsd

說明:http://fy.custom.com/schema/fy 自定義的,可以隨便命名,但是要保持前後統一哦
可以參考org/springframework/beans/factory/xml/spring-beans.xsd

<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://fy.custom.com/schema/fy"
			xmlns:xsd="http://www.w3.org/2001/XMLSchema"
			targetNamespace="http://fy.custom.com/schema/fy"
			elementFormDefault="qualified"
			attributeFormDefault="unqualified">
	<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
	<!--在這裡模仿spring的bean標籤的定義,自己寫一個-->
	<xsd:element name="bean">
		<xsd:complexType>
			<xsd:complexContent>
				<!--宣告id-->
				<xsd:extension base="identifiedType">
					<!--宣告子元素-->
					<xsd:group ref="beanElements"/>
					<!--宣告其他屬性-->
					<xsd:attributeGroup ref="beanAttributes"/>
				</xsd:extension>
			</xsd:complexContent>
		</xsd:complexType>
	</xsd:element>

	<xsd:attributeGroup name="beanAttributes">
		<xsd:attribute name="name" type="xsd:string">
			<xsd:annotation>
				<xsd:documentation><![CDATA[
	Can be used to create one or more aliases illegal in an (XML) id.
	Multiple aliases can be separated by any number of spaces, commas,
	or semi-colons (or indeed any mixture of the three).
				]]></xsd:documentation>
			</xsd:annotation>
		</xsd:attribute>
		<xsd:attribute name="class" type="xsd:string">
			<xsd:annotation>
				<xsd:documentation source="java:java.lang.Class"><![CDATA[
	The fully qualified name of the bean's class, except if it serves only
	as a parent definition for child bean definitions.
				]]></xsd:documentation>
			</xsd:annotation>
		</xsd:attribute>
		<xsd:anyAttribute namespace="##other" processContents="lax"/>
	</xsd:attributeGroup>

	<xsd:complexType name="identifiedType" abstract="true">
		<xsd:annotation>
			<xsd:documentation><![CDATA[
	The unique identifier for a bean. The scope of the identifier
	is the enclosing bean factory.
			]]></xsd:documentation>
		</xsd:annotation>
		<xsd:attribute name="id" type="xsd:string">
			<xsd:annotation>
				<xsd:documentation><![CDATA[
	The unique identifier for a bean. A bean id may not be used more than once
	within the same <beans> element.
				]]></xsd:documentation>
			</xsd:annotation>
		</xsd:attribute>
	</xsd:complexType>

	<xsd:group name="beanElements">
		<xsd:sequence>
			<xsd:choice minOccurs="0" maxOccurs="unbounded">
				<xsd:element ref="property"/>
				<xsd:any namespace="##other" processContents="strict" minOccurs="0" maxOccurs="unbounded"/>
			</xsd:choice>
		</xsd:sequence>
	</xsd:group>

	<xsd:element name="property">
		<xsd:complexType>
			<xsd:attribute name="name" type="xsd:string"/>
			<xsd:attribute name="value" type="xsd:string"/>
		</xsd:complexType>
	</xsd:element>
</xsd:schema>

2. 實現自定義的解析處理類,解析自己定義的標籤

自定義標籤解析類,通過繼承AbstractSingleBeanDefinitionParser 的方式

package com.fy.test.handler;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;

public class FYCustomBeanParser extends AbstractSingleBeanDefinitionParser {
	public static final String CLASS_ATTRIBUTE = "class";

	@Override
	protected String getBeanClassName(Element element) {
		if (element.hasAttribute(CLASS_ATTRIBUTE)){
			return element.getAttribute(CLASS_ATTRIBUTE);
		}
		return null;
	}

	@Override
	protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
	    // 因為我定義的標籤的子元素是property,所以可以直接呼叫spring的方法來解析
		parserContext.getDelegate().parsePropertyElements(element, builder.getBeanDefinition());
	}
}

自定義標籤解析處理類,用來指定自定義標籤的用哪個解析類來解析,通過繼承NamespaceHandlerSupport 的方式

package com.fy.test.handler;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

public class FYCustomBeanHandler extends NamespaceHandlerSupport {

	@Override
	public void init() {
		// 指定FYCustomBeanParser來解析自定義的bean標籤
		registerBeanDefinitionParser("bean", new FYCustomBeanParser());
	}
}

3. 在META-INF中做一些配置,讓Spring在解析自定義的標籤時,通過自己定義的解析類進行解析

spring.handlers

http\://fy.custom.com/schema/fy=com.fy.test.handler.FYCustomBeanHandler

spring.schemas

http\://fy.custom.com/schema/fy.xsd=config/fy.xsd

4. 使用自定義標籤fy-beans.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:fy="http://fy.custom.com/schema/fy"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
	   http://fy.custom.com/schema/fy http://fy.custom.com/schema/fy.xsd">

	<bean id="teacher" class="com.fy.test.model.Teacher">
		<property name="student" ref="student"/>
	</bean>
	<bean id="student" class="com.fy.test.model.Student">
		<property name="name" value="張三"/>
		<property name="age" value="18"/>
	</bean>
	<!--自定義的bean標籤-->
	<fy:bean id="student" class="com.fy.test.model.Student">
		<fy:property name="name" value="李四"/>
		<fy:property name="age" value="20"/>
	</fy:bean>
</beans>

5. 測試

package com.fy.test;

import com.fy.test.model.Student;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class FYCustomTest {
   private final static String FY_BEANS_XML = "fy-beans.xml";

   @Test
   public void testGetBean() {
   	ApplicationContext context = new ClassPathXmlApplicationContext(FY_BEANS_XML);
   	Student student = context.getBean("student", Student.class);
   	System.out.println(student);
   }
}

6. 結果OK

說明

spring-test-fy.gradle需要新增spring-context的依賴

可參考 spring-test.gradle

我是在Spring原始碼5.2.x版本中測試的,一直報這個錯,始終編譯不過,一度懷疑人生。

gradle下的docs.gradle檔案中第220行的校驗註釋掉,就可以了

自定義標籤解析的原理


重點在這個resolve(String namespaceUri)方法裡

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

在解析的時候

public BeanDefinition parse(Element element, ParserContext parserContext) {
		BeanDefinitionParser parser = findParserForElement(element, parserContext);
		return (parser != null ? parser.parse(element, parserContext) : null);
	}
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;
	}