The default tag is the default tag. The default tag is the default tag. The default tag is the default tag.

As we know, the core modules of Spring source are spring-core and Spring-Beans, and other modules derived from this, such as context, Cache, TX, etc., are based on these two basic modules.

As smart as you are, you should think of the @cacheable annotation, @Transaction annotation that we use in our code, and alibaba’s RPC middleware Dubbo, Register and subscribe services in configuration files through
or
, which are Spring’s implementation of custom tags. Custom tags are much more powerful!

As a programmer with pursuit, of course, can not be satisfied with the default tag framework, in order to expand and configuration requirements, this time need to learn to customize tags and use custom tags ~


The official example

First, take a look at the source code image (red box is important yo)

< myName :>






< MVC > and < myName > are all custom tags. On the left is the configuration file that defines the bean. The XMLNS at the top is the namespace that represents the definition file of the tag.

whilemynameEquivalent to the balm, can be defined as a transaction, and can be defined as a cache, as long as we carry out the corresponding definition in the namespace can be correctly identified. This is the custom tag we will use later, using the namespace to locate the desired processing logic.

< XSD: Element name=”annotation-driven”> defines elements. < XSD :complexType> defines attribute lists. < XSD: Attribute > defines individual attributes. For detailed analysis, please refer to the notes ~

On the right is the XSD file for the transaction definition. The general content is the same as in the middle. Although the element name

is the same, the following attribute definitions are different.

So we have a general idea of custom annotations. The XSD description file is one of the keys. The namespace at the top of the configuration file is the configuration where tags are parsed, where they are positioned, and of course, where handlers are used, as described below.

Do not know the understanding of the right, if there is an error, please big guys point out, I will modify!


Custom label usage

Spring provides support for extensible schemas. Extending Spring custom tag configurations requires the following steps:

  • Create a component that needs to be extended
  • To define aXSDDescription file
  • Create a file to implementBeanDefinitionParseInterface for parsingXSDDefinitions and component definitions in files.
  • To create aHandlerFile, extended fromNamespaceHandlerSupportRegisters the component toSpringThe container
  • writeSpring.handlersSpring.schemasfile

When I first saw these procedures, I was a little bit panicked, after all, from a cute new tag with default, suddenly I have to define my own, so please follow the process below to see


Define ordinary POJO components

This is nothing to say, just a common class:

public class Product {

	private Integer productId;

	private String unit;

	private String name;
}
Copy the code

defineXSDDescription file

custom-product.xsd

<xsd:schema targetNamespace="http://vip-augus.github.io/schema/product"
			xmlns:xsd="http://www.w3.org/2001/XMLSchema"
			elementFormDefault="qualified">
	<! Comment 3.4 Custom elements -->
	<xsd:element name="product">
		<xsd:complexType>
            <! -- This is the name of the class when it was registered.
			<xsd:attribute name="id" type="xsd:string"/>
			<! Attribute definition list, name and type -->
			<xsd:attribute name="productId" type="xsd:integer"/>
			<xsd:attribute name="unit" type="xsd:string"/>
			<xsd:attribute name="name" type="xsd:string"/>
		</xsd:complexType>
	</xsd:element>
</xsd:schema>
Copy the code

In the description file above, I defined a new targetNamespace, along with a new element called Product, and listed the attributes in the component in < XSD: Attribute >. The XSD file is an alternative to the XML DTD, which I won’t go into too much detail for those of you who are interested.


Define the component parser

base.label.custom.ProductBeanDefinitionParser

public class ProductBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {

	@Override
	protected Class getBeanClass(Element element) {
		// Return the corresponding type
		return Product.class;
	}

	// Parse and extract the corresponding element from element
	@Override
	protected void doParse(Element element, BeanDefinitionBuilder builder) {

		String productId = element.getAttribute("productId");
		String productName = element.getAttribute("name");
		String productUnit = element.getAttribute("unit");
		// Place the extracted data in the BeanDefinitionBuilder. After all beans have been parsed, register them with the beanFactory
		if(productId ! =null) {
			// Element.getAttribute ("") returns string values
			builder.addPropertyValue("productId", Integer.valueOf(productId));
		}
		if (StringUtils.hasText(productName)) {
			builder.addPropertyValue("name", productName);
		}
		if (StringUtils.hasText(productUnit)) {
			builder.addPropertyValue("unit", productUnit); }}}Copy the code

Key point is that our parser is AbstractSingleBeanDefinitionParser inheritance and overloading the two methods, please see comments ~ detailed purposes


Create a registry for the processing class

base.label.custom.ProductBeanHandler

public class ProductBeanHandler extends NamespaceHandlerSupport {

	@Override
	public void init(a) {
		// Register the component parser with the 'Spring' container
		registerBeanDefinitionParser("product".newProductBeanDefinitionParser()); }}Copy the code

This class is also relatively simple, but it extends NamespaceHandlerSupport to register the component parser with the Spring container when the class is initialized.


writespring.hanldersspring.schemasfile

I put the file location in the resources -> meta-INF directory:

spring.handlers

http\://vip-augus.github.io/schema/product=base.label.custom.ProductBeanHandler
Copy the code

spring.schemas

http\://vip-augus.github.io/schema/product.xsd=custom/custom-product.xsd
Copy the code

At this point, the custom configuration ends. How is it used


Use the Demo

The configuration file

<?xml version="1.0" encoding="UTF-8"? >
<! -- Notice the schema location, the last two lines are my new custom configuration -->
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:myname="http://vip-augus.github.io/schema/product"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://vip-augus.github.io/schema/product http://vip-augus.github.io/schema/product.xsd">

	<! -- Custom tag usage -->
	<myname:product id="product" productId="1" name="Apple" unit="Taiwan"/>
</beans>
Copy the code

The test code

public class ProductBootstrap {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("custom/custom-label.xml");
		Product product = (Product) context.getBean("product");
		// output Product{, productId ='1', unit=' table ', name='Apple'}System.out.println(product.toString()); }}Copy the code

summary

To recap, Spring encounters custom tags in the general flow of loading custom tags:

  • positioningspring.hanldersspring.schemas: Find the corresponding in the two fileshandlerXSD, the default location isresources -> META-INF.
  • HandlerregisteredParserExpanded the:NamespaceHandlerSupportClass at initialization to register the parser
  • Run the parserParserExpanded the:AbstractSingleBeanDefinitionParser, through overloaded methods for attribute resolution, complete the resolution.

The use of custom annotations has been covered above, and the next step is how to parse custom tags in the source code.


Custom label resolution

In my last note, I explained how to parse the default tag. If Spring determines that a tag is not the default tag, it will pass the tag resolution to the custom tag resolution method

Navigate directly to the method of parsing custom tags:

org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition)

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
		Note 3.8 ① Find the namespace
		String namespaceUri = getNamespaceURI(ele);
		// find the corresponding NamespaceHandler based on the namespace
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		// ③ Call the custom NamespaceHandler for parsing
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}
Copy the code

Look at the process is not familiar, we just used the custom tag, the file order is the same, the following three methods, the specific code will not be posted too much, mainly record some key methods and processes, detailed code and process please download the project I uploaded ~


① Obtain the namespace of the label

public String getNamespaceURI(Node node) {
		return node.getNamespaceURI();
	}
Copy the code

What this method does is very simple, and the type org.w3c.dom.node is already available, so we just need to call it.


② Find the corresponding NamespaceHandler based on the namespace

Specific parse methods in this class:

org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver#resolve

public NamespaceHandler resolve(String namespaceUri) {
	// Comment 3.9 Obtaining all configured Handler mappings
	Map<String, Object> handlerMappings = getHandlerMappings();
	// Retrieve the className of the NamespaceHandler corresponding to the namespace from the map
	// The mapping map value, if not, will be instantiated and put into the map, so that the next time the same namespace can be used directly
	Object handlerOrClassName = handlerMappings.get(namespaceUri);
	if (handlerOrClassName == null) {
		return null;
	}
	else if (handlerOrClassName instanceof NamespaceHandler) {
		return (NamespaceHandler) handlerOrClassName;
	}
	else{ String className = (String) handlerOrClassName; 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");
		}
		// instantiate the class
		NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
		// Call handler's init() method
		namespaceHandler.init();
		// Put it into the handler map
		handlerMappings.put(namespaceUri, namespaceHandler);
		returnnamespaceHandler; }}Copy the code

To find the corresponding NamespaceHandler, the key method is getHandlerMappings() :

private Map<String, Object> getHandlerMappings(a) {
	Map<String, Object> handlerMappings = this.handlerMappings;
	// If there is no cache, cache loading, public variables, lock operations, details 👍
	if (handlerMappings == null) {
		synchronized (this) {
			handlerMappings = this.handlerMappings;
			if (handlerMappings == null) {
				Properties mappings =
						PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
				handlerMappings = new ConcurrentHashMap<>(mappings.size());
				CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
				this.handlerMappings = handlerMappings; }}}return handlerMappings;
}
Copy the code

So we can see that when we look for a Handler, the strategy we use is lazy loading, we find it in the map cache, we return it, we don’t find it, we instantiate the Handler, we execute init(), and then we put the Handler in the map cache and wait for the next Handler to be used.


3. Call the customized NamespaceHandler for parsing

Recall that we didn’t override the parse() method when we customized tag parsing, so navigate in and see that the actual call methods are these two lines:

org.springframework.beans.factory.xml.NamespaceHandlerSupport#parse

public BeanDefinition parse(Element element, ParserContext parserContext) {
		// Find the parser and parse
		BeanDefinitionParser parser = findParserForElement(element, parserContext);
		// Actually parse the method called by the call
		return(parser ! =null ? parser.parse(element, parserContext) : null);
	}
Copy the code

The first step is to get the parser that we registered with the Spring container in the init() method.

The second step is the parser for parsing method, our parser extension is AbstractSingleBeanDefinitionParser, So the actual is to call our parent parser parent AbstractBeanDefinitionParser parse method:

org.springframework.beans.factory.xml.AbstractBeanDefinitionParser#parse

public final BeanDefinition parse(Element element, ParserContext parserContext) {
		// Comment 3.10 The actual custom tag parser calls a method that, in the parseInternal method, calls our overloaded methodAbstractBeanDefinition definition = parseInternal(element, parserContext); .return definition;
}
Copy the code

Parsing key methods

org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser#parseInternal

protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
	BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
	String parentName = getParentName(element);
	if(parentName ! =null) { builder.getRawBeanDefinition().setParentName(parentName); } Class<? > beanClass = getBeanClass(element);if(beanClass ! =null) {
		builder.getRawBeanDefinition().setBeanClass(beanClass);
	}
	else {
		String beanClassName = getBeanClassName(element);
		if(beanClassName ! =null) {
			builder.getRawBeanDefinition().setBeanClassName(beanClassName);
		}
	}
	builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
	BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
	if(containingBd ! =null) {
		// Inner bean definition must receive same scope as containing bean.
		builder.setScope(containingBd.getScope());
	}
	if (parserContext.isDefaultLazyInit()) {
		// Default-lazy-init applies to custom bean definitions as well.
		builder.setLazyInit(true);
	}
	// Comment 3.11 calls the parsing method we wrote here
	doParse(element, parserContext, builder);
	return builder.getBeanDefinition();
}
Copy the code

I’m going to go backwards here, but in the second step of parsing, custom is not called directlydoParseMethod, but a series of data preparation, includingbeanClass,class,lazyInitProperties such as preparation.

The first parsing, in the code I omitted, is to wrap the results of the second parsing fromAbstractBeanDefinitionConverted toBeanDefinitionHolderAnd then register. The conversion and registration process was covered in the first note and will not be covered again.

So far, our custom tag parsing is complete


conclusion

When we customize tags, does it feel easy to just define a few files, write the business logic in the custom parser, and then use it?

As we go through the entire parsing process,SpringWe did a lot of things behind the scenes, like default tag parsing, finding the corresponding processor based on the namespace, then finding the parser, and calling our personalized processing logic in the parser.

These two articles fill in the default tag and custom tag parsing holes. They also cover the whole process of Spring loading beans from configuration into memory. The next article starts with parsing classes

Due to limited personal skills, if there is any misunderstanding or mistake, please leave a comment, and I will correct it according to my friends’ suggestions

Spring-analysis-note cloud Gitee address

Spring – analysis – note making address


The resources

  1. Spring custom tag usage and principle

  2. — Beijing: Posts and Telecommunications Press


Portal:

  • Spring source learning – environment preparation

  • (1) The infrastructure of the container

  • Spring source code learning (2) default tag parsing

  • Spring source code learning (3) custom tags

  • Spring source code learning (four) bean loading

  • Spring source code learning (5) loop dependency

  • Spring source code learning (six) extension function part 1

  • Spring source code learning (seven) extension features part 2

  • Spring source learning (eight) AOP use and implementation principle

  • Spring source code learning (9) Transaction Transaction

  • (10) Spring MVC