Spring源码学习-自定义标签实践及原理

Spring-Framework下新建Module

先看下工程结构
Spring源码学习-自定义标签实践及原理

代码

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源码学习-自定义标签实践及原理

说明

spring-test-fy.gradle需要添加spring-context的依赖

可参考 spring-test.gradle

Spring源码学习-自定义标签实践及原理

我是在Spring源码5.2.x版本中测试的,一直报这个错,始终编译不过,一度怀疑人生。
Spring源码学习-自定义标签实践及原理

gradle下的docs.gradle文件中第220行的校验注释掉,就可以了

Spring源码学习-自定义标签实践及原理

自定义标签解析的原理

Spring源码学习-自定义标签实践及原理

Spring源码学习-自定义标签实践及原理
重点在这个resolve(String namespaceUri)方法里
Spring源码学习-自定义标签实践及原理

Spring源码学习-自定义标签实践及原理

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;
	}
上一篇:专题 二分图 + 网络流


下一篇:使用JQ+JS中的高级数组实现下拉列表框(select)的省市联动