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
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.
whilemyname
Equivalent 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 a
XSD
Description file - Create a file to implement
BeanDefinitionParse
Interface for parsingXSD
Definitions and component definitions in files. - To create a
Handler
File, extended fromNamespaceHandlerSupport
Registers the component toSpring
The container - write
Spring.handlers
和Spring.schemas
file
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
defineXSD
Description 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.hanlders
和 spring.schemas
file
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:
- positioning
spring.hanlders
和spring.schemas
: Find the corresponding in the two fileshandler
和XSD
, the default location isresources
->META-INF
. Handler
registeredParser
Expanded the:NamespaceHandlerSupport
Class at initialization to register the parser- Run the parser
Parser
Expanded 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 directlydoParse
Method, but a series of data preparation, includingbeanClass
,class
,lazyInit
Properties such as preparation.
The first parsing, in the code I omitted, is to wrap the results of the second parsing fromAbstractBeanDefinition
Converted toBeanDefinitionHolder
And 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,Spring
We 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
-
Spring custom tag usage and principle
-
— 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