What is a Spring placeholder?

In previous Spring Xml configurations we might have had 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> true" location="classpath:jdbc.properties"/> jdbc" class="com.john.properties.jdbcBean" > url" value="${jdbc.url}"/>  Copy the code

In the configuration above, the url property value of the JDBC Bean ${jdbc.url} represents the placeholder, The actual value of the placeholder is stored in the jdbc.properties configuration file represented by the location attribute of the context:property-placeholder custom element, which contains a single line:

The JDBC url = 127.0.0.1Copy the code

The question is, at what stage does Spring parse and replace placeholders with actual values?

When does Spring parse and hold placeholders

Context :place-holder context:place-holder context:place-holder context:place-holder context:place-holder context:place-holder context:place-holder context:place-holder The code in the BeanDefinitionParserDelegate this class:

/**
	 * 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 you belong to a beans namespace
					if (delegate.isDefaultNamespace(ele)) {
						// Process the default tags
						parseDefaultElement(ele, delegate);
					}
					else {
						// Customize the tag
						// Parser is used
						// Todo Parser internally registers BeanDefinition 2021-3-15delegate.parseCustomElement(ele); }}}}else{ delegate.parseCustomElement(root); }}Copy the code

Main focus: delegate. ParseCustomElement (ele);

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

	// It is also possible for todo property child elements to parse custom elements parsePropertySubElement
	@Nullable
	public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
		String namespaceUri = getNamespaceURI(ele);
		if (namespaceUri == null) {
			return null;
		}
		// There is an initialization in resolve
		// Get the NamespaceHandler according to the namespace URI

		// Todo gets a custom handler from the namespace such as ContextNamespaceHandler
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			Handlers will report this error 2020-09-14 if there is no handler in the spring.Handlers configuration file that defines the command space
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
		// Call the parse method
		// Here ParserContext is injected into Registry
		ReaderContext ->XmlBeanDefinitionReader contains registry
		//TODO initializes a ParserContext
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}
Copy the code

Now we’re going to focus on how this NamespaceHandler gets it. Spring gets the NamespaceHandlerResolver from the current readerContext and then obtains the currently appropriate NamespaceHandler through its resolve method based on 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 in the beanRegistry
		//beanRegistry defines an important method, registerBeanDefinition
		return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
				this.sourceExtractor, this, getNamespaceHandlerResolver());
	}

	/ * * * Lazily create a default NamespaceHandlerResolver, if not the set before. Use * * parsed when the custom tag@see #createDefaultNamespaceHandlerResolver()
	 */
	public NamespaceHandlerResolver getNamespaceHandlerResolver(a) {
		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(a) { ClassLoader cl = (getResourceLoader() ! =null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
		return new DefaultNamespaceHandlerResolver(cl);
	}

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

From the code above knowable Spring is using the default DefaultNamespaceHandlerResolver, of course it also left the developer customize NamespaceHandlerResolver opportunities. That we can now see how DefaultNamespaceHandlerResolver according to the analytical to the corresponding namespaceUri NamespaceHandler.

Context: the first place – holder is available is the context namespace, full path for http://www.springframework.org/schema/context. We can see that it is from DefaultNamespaceHandlerResolver class how to parse the 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 handler calls 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(a) {
		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 is empty to 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;
	}
Copy the code

The handlerMappingsLocation in the code above is generally the default path for Spring:

	// Specifies the default handler path that can be passed in to change the specified path
	/** * 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";

Copy the code

We go back to the Spring.Handlers file in the Resoures/meta-INF folder of the Spring-Context project and define 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
Copy the code

You can see the context custom namespace is the corresponding ContextNamespaceHandler. Let’s open this class and take a look:

public class ContextNamespaceHandler extends NamespaceHandlerSupport {

	@Override
	public void init(a) {
		// Call the registered parser method of the abstract class NamespaceHandlerSupport
		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".newMBeanServerBeanDefinitionParser()); }}Copy the code

It just defines an init method, which means to initialize, so when does it initialize? We go back to DefaultNamespaceHandler’s resolve method and find a **namespaceHandler.init() inside; , ** here the corresponding namespace handler initialization method is performed. Next we have to take a look at the initialization do what, we find that it calls the same method registerBeanDefinitionParser is registered Bean definitions parser, here we first press the pause button, and then smoothed her above the overall process of:

  1. Spring looks for the appropriate NamespaceHandler based on the custom namespace when parsing custom tags.
  2. The custom NamespaceHandler is resolved by the NamespaceHandlerResolver.
  3. Handlers are found in the NamespaceHandlerResolver based on classLoader.getResources.
  4. After that, convert the file content to handlerMappings and then match it to the NamespaceHandler based on the custom namespace passed in
  5. Execute the Init method of NamespaceHandler to register BeanDefinitionParser.

What’s so powerful about the Parser? We’ll do that next time.