Explain how Spring resolves placeholders in detail

  • 2021-09-24 22:31:16
  • OfStack

Directory What is the placeholder for Spring?
When does Spring parse and placeholders

What are the placeholders of Spring?

In the previous Spring Xml configuration, we might have the following configuration:


<?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:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd>
    <context:property-placeholder ignore-unresolvable="true"   location="classpath:jdbc.properties"/>

    <bean id="jdbc"  class="com.john.properties.jdbcBean" >
        <property name="url" value="${jdbc.url}"/>
    </bean></beans>

In the above configuration, the url attribute value of jdbc, Bean, ${jdbc. url}, represents the placeholder, and the true value of the placeholder is stored in the configuration file jdbc. properties, which is represented by the location attribute of the custom element in the above configuration. There is only one line in this configuration file:


jdbc.url=127.0.0.1

Then the question arises, at what stage does Spring parse and replace placeholders with actual values?

When does Spring parse and placeholders

From us, we can know that it is a custom xml tag, so Spring must parse it, so I will directly paste the entry code of Spring to parse this custom element for you readers. This code is in the class BeanDefinitionParserDelegate:


/**
     * Parse the elements at the root level in the document:
     * "import", "alias", "bean".
     * @param root the DOM root element of the document
     */
    //todo doRegisterBeanDefinitions ->  parseBeanDefinitions -> parseDefaultElement
    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        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 it belongs to beans Namespace 
                    if (delegate.isDefaultNamespace(ele)) {
                        // Handling Default Labels 
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        // Custom label 
                        // Used parser
                        //todo parser Internal   To register BeanDefinition 2021-3-15
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

Main focus: delegate. parseCustomElement (ele);


    @Nullable
    public BeanDefinition parseCustomElement(Element ele) {
        return parseCustomElement(ele, null);
    }

    //todo property  Child element   It's also possible   Resolve custom elements  parsePropertySubElement
    @Nullable
    public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
        String namespaceUri = getNamespaceURI(ele);
        if (namespaceUri == null) {
            return null;
        }
        //resolve There is an initialization process in 
        // Based on the namespace uri Get  NamespaceHandler

        //todo  Gets the customization under the namespace handler  For example  ContextNamespaceHandler
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            //todo  If in spring.handlers Configuration file   That does not define this command space in the handler It will   Report this mistake  2020-09-14
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        // Call parse Method 
        // Here ParserContext Injection registry
        //readerContext Li  reader->XmlBeanDefinitionReader  It contains  registry
        //TODO  Initializes 1 A ParserContext Go in 
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }

Here, we should pay attention to how this NamespaceHandler is obtained. From the above code, we can see that Spring can obtain the current suitable NamespaceHandler through its resolve method and according to the current namespaceUri passed in.


/**
     * Create the {@link XmlReaderContext} to pass over to the document reader.
     */
    public XmlReaderContext createReaderContext(Resource resource) {
        // Put the current reader Put it in  ,reader Stored  beanRegistry
        //beanRegistry  It defines the  registerBeanDefinition  This important method 
        return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
                this.sourceExtractor, this, getNamespaceHandlerResolver());
    }

    /**
     * Lazily create a default NamespaceHandlerResolver, if not set before.
     *  Used when parsing custom labels 
     * @see #createDefaultNamespaceHandlerResolver()
     */
    public NamespaceHandlerResolver getNamespaceHandlerResolver() {
        if (this.namespaceHandlerResolver == null) {
            this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
        }
        return this.namespaceHandlerResolver;
    }

    /**
     * Create the default implementation of {@link NamespaceHandlerResolver} used if none is specified.
     * <p>The default implementation returns an instance of {@link DefaultNamespaceHandlerResolver}.
     * @see DefaultNamespaceHandlerResolver#DefaultNamespaceHandlerResolver(ClassLoader)
     */
    protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
        ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
        return new DefaultNamespaceHandlerResolver(cl);
    }

    // Returns the default 
    public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) {
        this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
    }

From the above code, we can see that Spring uses the default DefaultNamespaceHandlerResolver, which certainly leaves developers with the opportunity to customize NamespaceHandlerResolver. Now we can see how DefaultNamespaceHandlerResolver resolves to the corresponding NamespaceHandler based on namespaceUri.

The first thing you get is the context namespace, and the complete path is http\://www.springframework.org/schema/context. We can see from the DefaultNamespaceHandlerResolver class how it resolves this namespace.


/**
     * 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
    @Nullable
    public NamespaceHandler resolve(String namespaceUri) {
        Map<String, Object> handlerMappings = getHandlerMappings();
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if (handlerOrClassName == null) {
            return null;
        }
        else if (handlerOrClassName instanceof NamespaceHandler) {
            return (NamespaceHandler) handlerOrClassName;
        }
        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");
                }
                NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
                //todo   Namespace processor   Call the initialization procedure  2020-09-04
                namespaceHandler.init();
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
            catch (ClassNotFoundException ex) {
                throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
                        "] for namespace [" + namespaceUri + "]", ex);
            }
            catch (LinkageError err) {
                throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
                        className + "] for namespace [" + namespaceUri + "]", err);
            }
        }
    }

    /**
     * Load the specified NamespaceHandler mappings lazily.
     */
    private Map<String, Object> getHandlerMappings() {
        Map<String, Object> handlerMappings = this.handlerMappings;
        if (handlerMappings == null) {
            synchronized (this) {
                handlerMappings = this.handlerMappings;
                if (handlerMappings == null) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Loading NamespaceHandler mappings from [" + this.handlerMappingsLocation + "]");
                    }
                    try {
                        //todo handlerMappings Empty   Just go   Get all attribute mappings  2020-09-04
                        //spring-aop spring-beans spring-context
                        Properties mappings =
                                PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Loaded NamespaceHandler mappings: " + mappings);
                        }
                        handlerMappings = new ConcurrentHashMap<>(mappings.size());
                        CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                        this.handlerMappings = handlerMappings;
                    }
                    catch (IOException ex) {
                        throw new IllegalStateException(
                                "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
                    }
                }
            }
        }
        return handlerMappings;
    }

handlerMappingsLocation1 in the above code is the default path of Spring:


    // Specifies the default handler Path   You can pass in a specified path change 
    /**
     * The location to look for the mapping files. Can be present in multiple JAR files.
     */
    public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

We go back to the spring. handlers file under the resoures/META-INF folder under the spring-context project, which defines the following rules:


http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

You can see that the context custom namespace is the corresponding ContextNamespaceHandler. Let's open this class and look at 1:


public class ContextNamespaceHandler extends NamespaceHandlerSupport {

    @Override
    public void init() {
        // Calling abstract classes NamespaceHandlerSupport Register parser method of 
        registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
        registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
        registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
        registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
        registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
        registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
    }
}

It is found that it only defines one init method, which means initialization as its name implies. When will it perform initialization? We go back to the resolve method of DefaultNamespaceHandler and find that there is one namespaceHandler. init () inside it; The initialization method of the corresponding namespace processor is executed. Next, let's look at what initialization did, and we found that it called the same method registerBeanDefinitionParser, which is to register

Bean defines the parser, where we first press the pause button and then stroke down the overall process above:

Spring will find the appropriate NamespaceHandler according to the custom namespace when parsing the custom tag. The custom NamespaceHandler is parsed by NamespaceHandlerResolver. NamespaceHandlerResolver looks for spring. handlers under all class paths based on classLoader. getResources. After finding it, convert the file contents to handlerMappings, and then match it to NamespaceHandler according to the custom namespace passed in Execute the init method of NamespaceHandler to register BeanDefinitionParser.

What powerful functions has this parser done? We'll break it down next time.

The above is a detailed explanation of Spring how to parse placeholders in detail, more information about Spring parsing placeholders please pay attention to other related articles on this site!


Related articles: