4 spring - Interpretation of custom tags

Posted by netxfly on Sun, 15 Sep 2019 05:00:21 +0200

Original Link: https://my.oschina.net/u/1590001/blog/268181

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

Topics: Spring xml Attribute Java