Explain how Spring resolves placeholders in detail
- 2021-09-24 22:31:16
- OfStack
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!