This extension, based on my deep understanding of the Spring Ioc series, focuses on the extensibility of Spring loading parsing beans. If you know some of the extension points in Spring before, but don’t have a complete understanding of them, believe me, after reading the entire extension, You’ll be able to become more proficient in the poses that you were previously unskilled in, and you’ll be able to learn many new poses.
Beans, beans, import, Alias, aop, and componentScan are all custom tags. It’s just an extension of Spring’s own other modules. In this article today, I’ll show you how to define your own XML tags.
In Spring, we define a tag of our own as follows:
- Define your own XSD file
- Define an entity class that corresponds to an XSD file
- Create realized BeanDefinitionParser classes (in fact, it would be better inherited abstract class AbstractBeanDefinitionParser), to resolve our custom tag
- Create a class that inherits NamespaceHandlerSupport to register the class we created above with the Spring container
- Write your own Spring.handlers and spring.Schemas
Let’s start with a custom XSD file:
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://demo1.example.com/schema1"
targetNamespace="http://demo1.example.com/schema1">
<xsd:complexType name="billType">
<xsd:attribute name="name" type="xsd:string">
</xsd:attribute>
<xsd:attribute name="age" type="xsd:int">
</xsd:attribute>
</xsd:complexType>
<xsd:element name="bill" type="billType">
</xsd:element>
</xsd:schema>
Copy the code
Let’s take a look at the XSD file stuff. XSD: Element, where name is the name of the tag, and type refers to the tag XSD :complexType, where there are two sub-tags that are XSD: Attribute, one of which is a string name, The other one represents an age of type int, which means bill has name and age in it. Also note the top few lines, the second XMLNS: XSD =”www.w3.org/2001/XMLSch…” You can write the url as you like, but keep it consistent with the targetNamespace in line 4.
Let’s look at the second entity class:
@Data
public class Model {
private String name;
private Integer age;
}
Copy the code
I believe this without further explanation. Let’s look directly at steps 3 and 4, the class created in step 3:
public class BillBeanDefinitionParser implements BeanDefinitionParser { private final Class<? > beanClass; public BillBeanDefinitionParser(Class<? > beanClass) {this.beanClass = beanClass;
}
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
genericBeanDefinition.setBeanClass(beanClass);
genericBeanDefinition.setLazyInit(false);
genericBeanDefinition.getPropertyValues().add("name", element.getAttribute("name"));
genericBeanDefinition.getPropertyValues().add("age", element.getAttribute("age"));
parserContext.getRegistry().registerBeanDefinition(beanClass.getName(),genericBeanDefinition);
return null; }}Copy the code
The class created in step 4
public class BillNameSpaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("bill".newBillBeanDefinitionParser(Model.class)); }}Copy the code
Handlers and Spring. Schemas
Spring. Handlers:
http\://demo1.example.com/schema1=com.example.demo.external1.BillNameSpaceHandler
Copy the code
Spring.schemas:
http\://demo1.example.com/schema1/mytag.xsd=META-INF/mytag.xsd
Copy the code
These two files are in the properties format, they and the original XSD should be in the meta-INF folder in the Resource directory, and the Handlers key should be the same as the XMLNS you defined in the XSD, The value must point to the full path of your own NameSpaceHandler. In Spring.schemas, the first part of the key is your own XMLNS, and the second part of mytag. XSD is your own XSD file name.
Then add our tag to application-context.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:billtag="http://demo1.example.com/schema1"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://demo1.example.com/schema1 http://demo1.example.com/schema1/mytag.xsd">
<! -- Another extremely simple XML ~ >
<bean id="aTest" class="com.example.demo.article2.entity.ATest" name="aTest">
</bean>
<billtag:bill name="bill.li" age="18"/>
<import resource="classpath*:application-context1.xml"></import>
</beans>
Copy the code
Finally, run the test code to see:
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("classpath*:application-context.xml");
Model model = (Model)applicationContext.getBean(Model.class.getName());
System.out.println(model.getAge());
System.out.println(model.getName());
}
Copy the code
If you get through this last step of the code, congratulations, you are now able to define a very simple XML tag yourself. Why is it simple? If we want to define a complex XML tag, we can define only two attributes and no child tags. Let me give you teach the way of studying, we return to our application – context. Go to the first line in the XML XMLNS, behind on http://www.springframework.org/schema/beans press CTRL can go in, We can find the definition of beans tags here, so we just need to change our XSD, and then remember to change our BeanDefinitionParser too.
And then we’re not done, we need to fill in the holes in the original from XML to BeanDefinition, where block 12 says we’re going to custom tag parseCustomElement, so you can understand what Spring is doing for us here, The code here is relatively simple if you understand the previous code:
Let’s revert to the XML to BeanDefinition block 12:
The code block1
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
/ / is looking at the tag to have "http://www.springframework.org/schema/beans", if you have, if you go
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// Parse the default tags
parseDefaultElement(ele, delegate);
}
else {
// Parse custom tags.delegate.parseCustomElement(ele); }}}}else {
// Parse custom tags.delegate.parseCustomElement(root); }}Copy the code
Let’s go straight to parsecuto element.
The code block2
// Call this method first
public BeanDefinition parseCustomElement(Element ele) {
return parseCustomElement(ele, null);
}
// Retune this
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
// 1. Get the namespace we defined
String namespaceUri = getNamespaceURI(ele);
/ / 2. Get the corresponding Handler according to namespaceUri
// readerContext is an instance of XmlReadContext
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
// 3. If handler is null, an exception is reported
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
Copy the code
The last instance of this handler is the NameSpaceHandlerSupport class we defined earlier. This class found our BeanDefinitionParser in the parse method. It then calls its parse method to parse our tag. Let’s focus on the code at 2, what is this readerContext in 2? This thing is actually an instance of XmlReaderContext, This was created by calling the createReaderContext method in XmlBeanDefinitionReader (XmlBeanDefinitionReader, createReaderContext, XmlBeanDefinitionReader, 4) in block 9 of the third article from XML to BeanDefinition:
The code block3
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
Copy the code
It’s just a very simple new, so let’s look at the method that the last argument calls, and that’s what we care about, because what this gets is the NameSpaceHandlerResolver that we got in the second block, We found the NameSpaceHandlerResolver back through this method is actually a DefaultNamespaceHandlerResolver, let’s take a look at its resolve method, is a code block 2 second place finally call the resolve method:
The code block4
public NamespaceHandler resolve(String namespaceUri) {
// Get all configured handlerMapping mappings
Map<String.Object> handlerMappings = getHandlerMappings();
// Get the corresponding handler from the nameSpace and use the above Test code debug to find that the handler is defined by ourselves
// The full path is provided in spring.handler
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
// This else if is to see if this handler has been used before, if so, it returns directly
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
// If not, initialize this handler
else {
String className = (String) handlerOrClassName;
try{ 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");
}
// reflection creates an instance
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// Init. Remember the init method we defined for NameSpaceHandler?
namespaceHandler.init();
// Put it in the cache
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "] not found", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "]: problem with handler class file or dependent class", err); }}}Copy the code
The next step is parsing, which is what we talked about before. At this point, the definition and parsing of custom XML tags have been analyzed.