Resolution of 1.0 custom labels.
In the previous section, we finished loading the spring default labels. Now we will start a new mileage, the spring custom labels analysis.
The code is as follows:
1 /** 2 * Parse the elements at the root level in the document: "import", "alias", "bean". 3 * 4 * @param root the DOM root element of the document 5 */ 6 protected void parseBeanDefinitions(Element root, 7 BeanDefinitionParserDelegate delegate) { 8 // Yes Bean Processing 9 if (delegate.isDefaultNamespace(root)) { 10 NodeList nl = root.getChildNodes(); 11 for (int i = 0; i < nl.getLength(); i++) { 12 Node node = nl.item(i); 13 if (node instanceof Element) { 14 Element ele = (Element) node; 15 if (delegate.isDefaultNamespace(ele)) { 16 // Yes Bean Processing,If Default 17 parseDefaultElement(ele, delegate); 18 } 19 else { 20 // Yes Bean Processing,If Custom 21 delegate.parseCustomElement(ele); 22 } 23 } 24 } 25 } 26 else { 27 delegate.parseCustomElement(root); 28 } 29 }
In this chapter, everything starts with a single line of code, delegate.parseCustomElement(ele);
1.1.0 First let's look at the use of custom tags.
1.1.1 Extending a Spring custom tag configuration generally requires the following steps, based on the definition of the extended Schema provided by Spring.
1. Create a component that needs to be extended.
Define an XSD file.
3. Create a file. Implement the BeanDefinitionParser interface to parse definitions and primary key definitions in the XSD file.
4. Create a Handler file that extends NameSpaceHandleSupport to register components in the Spring container.
5. Write Spring.handlers and Spring.schemas files
1.1.1 User-defined labels.
1. First we create a normal pojo
1 package cn.c.bean; 2 3 public class MJorcen { 4 private String name; 5 private String alias; 6 private String code; 7 8 public String getName() { 9 return name; 10 } 11 12 public void setName(String name) { 13 this.name = name; 14 } 15 16 public String getAlias() { 17 return alias; 18 } 19 20 public void setAlias(String alias) { 21 this.alias = alias; 22 } 23 24 public String getCode() { 25 return code; 26 } 27 28 public void setCode(String code) { 29 this.code = code; 30 } 31 32 }
2. Define an xsd file description.
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.example.org/MJorcen" xmlns:tns="http://www.example.org/MJorcen" elementFormDefault="qualified"> <xsd:element name="mjorce"> <xsd:complexType> <xsd:sequence> <xsd:element name="alias" type="xsd:string"></xsd:element> <xsd:element name="code" type="xsd:string"></xsd:element> </xsd:sequence> <xsd:attribute name="name " type="xsd:string"></xsd:attribute> <xsd:attribute name="id" type="xsd:string"></xsd:attribute> </xsd:complexType> </xsd:element> </xsd:schema>
Note: Do not release without ID.Spring-4.0.0;
3. Create a class that implements the beanDefinitionParse interface to parse the definitions and primary key definitions in the xsd file. Usually we choose inheritance, which is implemented by AbstractSimpleBeanDefinitionParser or AbstractSingleBeanDefinitionParser
package cn.c.parse; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.AbstractSimpleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import cn.c.bean.MJorcen; public class MJorcenNamespacesprase extends AbstractSimpleBeanDefinitionParser { @Override protected Class<?> getBeanClass(Element element) { return MJorcen.class; } @Override protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { String name = element.getAttribute("name"); NodeList eles = element.getChildNodes(); String alias = eles.item(0).getTextContent(); String code = eles.item(1).getTextContent(); builder.addPropertyValue("name", name); builder.addPropertyValue("alias", alias); builder.addPropertyValue("code", code); } }
4. Create a class that implements the NamespaceHandlerSupport interface to register the build with the Spring container
package cn.c.parse; import org.springframework.beans.factory.xml.NamespaceHandlerSupport; public class MJorcenNamespacesHandlers extends NamespaceHandlerSupport { public static void main(String[] args) { System.out.println(MJorcenNamespacesHandlers.class); } public void init() { registerBeanDefinitionParser("mjorce", new MJorcenNamespacesprase()); } }
5. Write spring.handlers and spring.schema files
spring.schema
MJorcen.xsd=cn\c\xml\MJorcen.xsd
spring.handlers
http\://www.example.org/MJorcen=cn.c.parse.MJorcenNamespacesHandlers
At this point, the custom configuration is over. Look for handler and XSD files in spring.schema,spring.handlers. The default location is under META-INF directory, find the corresponding handler and parser for parsing elements, and complete the whole custom label parsing, that is, customization is different from the default standard in Spring.In Spring, custom tag resolution is delegated to users for implementation.
6. Create xml file:
<?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:c="http://www.example.org/MJorcen" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.example.org/MJorcen MJorcen.xsd"> <c:mjorce id="mj" name="MichealJorcen"> <c:alias>jorcen</c:alias> <c:code>088794</c:code> </c:mjorce> </beans>
7. Create test files
package cn.c.main; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import cn.c.bean.MJorcen; public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext( "cn/c/xml/applicationContxt.xml"); MJorcen f = (MJorcen) ctx.getBean("MichealJorcen"); System.out.println(f); } }
Below 2.0, let's go into the parseCustomElement method with the following code:
public BeanDefinition parseCustomElement(Element ele) { //containingBd Is Parent Bean ,Top element set to null return parseCustomElement(ele, null); }
Follow it down as follows:
/** * Parse the merge attribute of a collection element, if any. */ public boolean parseMergeAttribute(Element collectionElement) { String value = collectionElement.getAttribute(MERGE_ATTRIBUTE); if (DEFAULT_VALUE.equals(value)) { value = this.defaults.getMerge(); } return TRUE_VALUE.equals(value); } public BeanDefinition parseCustomElement(Element ele) { //containingBd Is Parent Bean ,Top element set to null return parseCustomElement(ele, null); } //containingBd Is Parent Bean ,Top element set to null public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { //Get the corresponding namespace String namespaceUri = getNamespaceURI(ele); //Find the corresponding namespace NamespaceHandler NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve( namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } // call NamespaceHandler Registered parse Resolve,[NamespaceHandlerSupport.java] return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
In fact, the idea is very simple, just get the corresponding namespace according to the corresponding bean s, then find the corresponding parser according to the namespace, and then parse according to the corresponding parser, which is easier said than done. Let's see the implementation.
2.1. Getting Namespaces
Tag parsing begins with namespaces, either Spring's default or custom, and is based on namespaces. As for how to do this, a good approach has been provided in org.w3c.dom.Node:
public String getNamespaceURI(Node node) { return node.getNamespaceURI(); }
2.2 Extract custom label processor.
/** * Locate the {@link NamespaceHandler} for the supplied namespace URI from the * configured mappings. * * @param namespaceUri the relevant namespace URI * @return the located {@link NamespaceHandler}, or {@code null} if none found */ @Override public NamespaceHandler resolve(String namespaceUri) { // Get all configured handler mapping Map<String, Object> handlerMappings = getHandlerMappings(); // Find corresponding information based on the namespace Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } else if (handlerOrClassName instanceof NamespaceHandler) { // Recent parsed cases read directly from the cache return (NamespaceHandler) handlerOrClassName; } else { // Not resolved,Returns a class path String className = (String) handlerOrClassName; try { // reflex,Convert to Class 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"); } // Initialization Class NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); // Call Custom NamespaceHandler init Method. namespaceHandler.init(); // Put in 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); } } }
Once you have a namespace processor, execute init() immediately to register the parser, such as:
public void init() { registerBeanDefinitionParser("mjorce", new MJorcenNamespacesprase()); }
Here, you can also register multiple parsers, such as <c:M, <c:N, etc. so that c's namespace can support multiple tag parsing.
After registration, the namespace processor can call different parsers depending on the label. Let's take a look at the getHandlerMappings method
/** * Load the specified NamespaceHandler mappings lazily. */ private Map<String, Object> getHandlerMappings() { // Start caching if it is not cached if (this.handlerMappings == null) { synchronized (this) { if (this.handlerMappings == null) { try { // this.handlerMappingsLocation Initialized in constructor=META-INF/spring.handlers Properties mappings = PropertiesLoaderUtils.loadAllProperties( this.handlerMappingsLocation, this.classLoader); if (logger.isDebugEnabled()) { logger.debug("Loaded NamespaceHandler mappings: " + mappings); } Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>( mappings.size()); //take Properties Format file merge into Map Formatted handlerMappings in CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); this.handlerMappings = handlerMappings; } catch (IOException ex) { throw new IllegalStateException( "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex); } } } } return this.handlerMappings; }
2.3 Label Resolution
Once you have the parser and the elements you need to analyze, Spring can delegate the parsing to a custom parser. The code is as follows:
// call NamespaceHandler Registered parse Resolve return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
During the parsing process, the parse method in the parser is invoked by first finding the parser corresponding to the element.
By combining instances, we actually get the corresponding Parse instance registered in the init method in the handler first. We call the parse method, but the custom namespace parser we implemented does not have a parse method, so we infer that this method is implemented in the parent class.
[NamespaceHandlerSupport.java]
@Override public BeanDefinition parse(Element element, ParserContext parserContext) { // Find Parser,And parse it return findParserForElement(element, parserContext).parse(element, parserContext); }
[NamespaceHandlerSupport.java]
During parsing, the parse method of the parser is invoked by first finding the parser corresponding to the element.
By combining instances, you start by getting the corresponding Parse instance registered in the init method in the handler and calling the parse method
/** * Locates the {@link BeanDefinitionParser} from the register implementations using * the local name of the supplied {@link Element}. */ private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { // Get element name,That is, in an instance:<c:mjorcen In mjorcen, String localName = parserContext.getDelegate().getLocalName(element); // according to mjorcen Find the corresponding parser,that is:registerBeanDefinitionParser("mjorce", new MJorcenNamespacesprase()); BeanDefinitionParser parser = this.parsers.get(localName); if (parser == null) { parserContext.getReaderContext().fatal( "Cannot locate BeanDefinitionParser for element [" + localName + "]", element); } return parser; }
For parse methods:
[AbstractBeanDefinitionParser.java]
@Override public final BeanDefinition parse(Element element, ParserContext parserContext) { AbstractBeanDefinition definition = parseInternal(element, parserContext); if (definition != null && !parserContext.isNested()) { try { // Get Elements Id attribute,Without,No release String id = resolveId(element, definition, parserContext); if (!StringUtils.hasText(id)) { parserContext.getReaderContext().error( "Id is required for element '" + parserContext.getDelegate().getLocalName(element) + "' when used as a top-level tag", element); } String[] aliases = new String[0]; String name = element.getAttribute(NAME_ATTRIBUTE); if (StringUtils.hasLength(name)) { aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); } // take AbstractBeanDefinition translate into BeanDefinitionHandler BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases); registerBeanDefinition(holder, parserContext.getRegistry()); if (shouldFireEvents()) { // The listener needs to be notified to process BeanComponentDefinition componentDefinition = new BeanComponentDefinition( holder); postProcessComponentDefinition(componentDefinition); parserContext.registerComponent(componentDefinition); } } catch (BeanDefinitionStoreException ex) { parserContext.getReaderContext().error(ex.getMessage(), element); return null; } } return definition; }
As you can see above, the real parsing is delegated to the function parseInternal
In parseInternal, instead of calling the custom doParse method directly, a series of data preparations have been made, including preparation for properties such as beanClass,scope,lazyInit, and so on.
@Override protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); String parentName = getParentName(element); if (parentName != null) { builder.getRawBeanDefinition().setParentName(parentName); } // Get from custom tags class,The custom parser is called,as , getBeanClass Method. Class<?> beanClass = getBeanClass(element); if (beanClass != null) { builder.getRawBeanDefinition().setBeanClass(beanClass); } else { // If the subclass is not implemented getBeanClass Then try to check if the subclass is new getBeanClassName Method. String beanClassName = getBeanClassName(element); if (beanClassName != null) { builder.getRawBeanDefinition().setBeanClassName(beanClassName); } } builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); if (parserContext.isNested()) { // Inner bean definition must receive same scope as containing bean. // If parent class exists,Then use the parent class's scope attribute builder.setScope(parserContext.getContainingBeanDefinition().getScope()); } if (parserContext.isDefaultLazyInit()) { // Default-lazy-init applies to custom bean definitions as well. //Delayed Loading builder.setLazyInit(true); } //Call subclass doParse Method. doParse(element, parserContext, builder); return builder.getBeanDefinition(); }
So far, Spring's parsing is complete. The next task is how to use these bean s.
Reprinted at: https://my.oschina.net/u/1590001/blog/268181