Spring startup process – The working resolution file name before loading the configuration file

When it comes to the Spring startup process, you start by loading the main configuration file.

To prepare the Demo

I have a little Demo here, and the structure of the project looks like this

And its configuration file is very simple


      
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
	<bean id="person" class="com.sourcodestudy.pojo.Person">
		<property name="name" value="Sdayup"/>
		<property name="age" value="22"/>
	</bean>
</beans>
Copy the code

Classes for testing

import com.sourcodestudy.pojo.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class StartDemo {

	public static void main(String[] args) {
		ApplicationContext ac =	new ClassPathXmlApplicationContext("classpath:application.xml");
		Person person = (Person) ac.getBean("person"); System.out.println(person.toString()); }}Copy the code

Analytical process

This all comes from the start of the ApplicationContext ac = new ClassPathXmlApplicationContext (” classpath: application. XML “); .

If you’ve used Spring, you’ll be familiar with this line of code that loads the Spring configuration file. It is the file that reads the classpath path, with or without the classpath prefix in the code above

What was done before you started loading the configuration file?

When following a breakpoint to the ClassPathXmlApplicationContext class, in the end they will go into such a constructor

public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

    super(parent);
    setConfigLocations(configLocations);
    if(refresh) { refresh(); }}Copy the code

Note: When you see a method that calls super, don’t skip it, because it might create some objects that you’ll need later, so it’s a good idea to look at it

So what’s super doing here? What the hell is it

public AbstractApplicationContext(@Nullable ApplicationContext parent) {
    this(a); setParent(parent); }Copy the code

When all lit super finally came to a class named AbstractApplicationContext, here will create some objects, to understand, behind may be used

  • Id: This is the only context inside the context
  • StartupShutdownMonitor: You can understand this as a refresh or destroy lock because the refresh or destroy process cannot be interrupted.

There are some other objects, if you’re interested

It then assigns the resource resolver when it sees the inside of this()

public AbstractApplicationContext(a) {
	this.resourcePatternResolver = getResourcePatternResolver();
}
Copy the code

In this getResourcePatternResolver () method is created inside a PathMatchingResourcePatternResolver object.

PathMatchingResourcePatternResolver main purpose is through the given file path or Ant style, by path to find the corresponding resources

There is a man named validating in AbstractXmlApplicationContext class, I think this is also a place to see, this is a sign of whether to use XML validation

private boolean validating = true;
/**
 * Set whether to use XML validation. Default is {@code true}.
 */
public void setValidating(boolean validating) {
    this.validating = validating;
}
Copy the code

So what we do in Super is we initialize some variables that we’re going to use later, so you’ll have to look at that.

What does the setConfigLocations method do?

This before you look at the class diagram of the ClassPathXmlApplicationContext, because a lot of method is to directly use the inside of the parent class method.

Enter setConfigLocations method, in fact has come here AbstractRefreshableApplicationContext class

/** * Set the config locations for this application context. * 

If not set, the implementation may use a default as appropriate. */

public void setConfigLocations(@Nullable String... locations) { if(locations ! =null) { Assert.noNullElements(locations, "Config locations must not be null"); this.configLocations = new String[locations.length]; for (int i = 0; i < locations.length; i++) { this.configLocations[i] = resolvePath(locations[i]).trim(); }}else { this.configLocations = null; }}Copy the code

This method passes in the path of the configuration files and puts them back inside configLocations,

  • ConfigLocations is a string array that stores the path to the configuration file

A method called resolvePath() is called. Why is there such a thing?

/**
 * Resolve the given path, replacing placeholders with corresponding
 * environment property values if necessary. Applied to config locations.
 * @param path the original file path
 * @return the resolved file path
 * @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)
 */
protected String resolvePath(String path) {
    return getEnvironment().resolveRequiredPlaceholders(path);
}
Copy the code

Some configuration files are named application${username} with a placeholder for the file name, which can be replaced here.

As written in the comment: Parse the given path and replace the placeholders with the corresponding environment attribute values if necessary.

The getEnvironment() method creates a standard environment object class that contains the system properties and environment properties

@Override
public ConfigurableEnvironment getEnvironment(a) {
    if (this.environment == null) {
        this.environment = createEnvironment();
    }
    return this.environment;
}

protected ConfigurableEnvironment createEnvironment(a) {
    return new StandardEnvironment();
}
Copy the code

Application ${SESSIONNAME} {application${SESSIONNAME}} {application${SESSIONNAME}} {application${SESSIONNAME} That means.

To resolveRequiredPlaceholders method after incoming configuration file filename, want to parse

Here for the convenience of the parameters to the application will show ClassPathXmlApplicationContext {USERNAME} {USERDOMAIN_ROAMINGPROFILE}. The XML

This property may not exist on other computers, just find a parameter that your computer has

@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    if (this.strictHelper == null) {
        this.strictHelper = createPlaceholderHelper(false);
    }
    return doResolvePlaceholders(text, this.strictHelper);
}
Copy the code

Tip: Whenever you see a method that starts with do, you’re ready to do the actual operation

Enter the method first to determine whether there is a placeholder helper object, if not to create, there is nothing to say here, is in this method to create an object. So if you look at it for yourself, it’s going to be a doresolveplaceholder () method.

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
    return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
Copy the code

So this is just calling the Replaceplaceholder method in this helper, which just looks at the parseStringValue method

public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
    Assert.notNull(value, "'value' must not be null");
    return parseStringValue(value, placeholderResolver, null);
}
Copy the code

The following method starts the placeholder substitution,

CreatePlaceholderHelper createPlaceholderHelper createPlaceholderHelper createPlaceholderHelper createPlaceholderHelper

private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;

private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;

private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
    return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
                                         this.valueSeparator, ignoreUnresolvablePlaceholders);
}
Copy the code

Remember I said above that when parsing filenames the system can replace **∗ {}**, how does it know to replace **∗ {}**, and not ##**? The reason is in the constructor parameter when it creates the placeholder helper.

}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}

When you look at the parseStringValue method the this.placeholderPrefix value in the first line is **${**

protected String parseStringValue(
			String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {

    int startIndex = value.indexOf(this.placeholderPrefix);
    if (startIndex == -1) {
        return value;
    }

    StringBuilder result = new StringBuilder(value);
    while(startIndex ! = -1) {
        int endIndex = findPlaceholderEndIndex(result, startIndex);
        if(endIndex ! = -1) {
            String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
            String originalPlaceholder = placeholder;
            if (visitedPlaceholders == null) {
                visitedPlaceholders = new HashSet<>(4);
            }
            if(! visitedPlaceholders.add(originalPlaceholder)) {throw new IllegalArgumentException(
                    "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
            }
            // Recursive invocation, parsing placeholders contained in the placeholder key.
            placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
            // Now obtain the value for the fully resolved key...
            String propVal = placeholderResolver.resolvePlaceholder(placeholder);
            if (propVal == null && this.valueSeparator ! =null) {
                int separatorIndex = placeholder.indexOf(this.valueSeparator);
                if(separatorIndex ! = -1) {
                    String actualPlaceholder = placeholder.substring(0, separatorIndex);
                    String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                    propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                    if (propVal == null) { propVal = defaultValue; }}}if(propVal ! =null) {
                // Recursive invocation, parsing placeholders contained in the
                // previously resolved placeholder value.
                propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                if (logger.isTraceEnabled()) {
                    logger.trace("Resolved placeholder '" + placeholder + "'");
                }
                startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
            }
            else if (this.ignoreUnresolvablePlaceholders) {
                // Proceed with unprocessed value.
                startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
            }
            else {
                throw new IllegalArgumentException("Could not resolve placeholder '" +
                                                   placeholder + "'" + " in value \"" + value + "\" ");
            }
            visitedPlaceholders.remove(originalPlaceholder);
        }
        else {
            startIndex = -1; }}return result.toString();
}
Copy the code

Start replacing placeholders in file names

This directory mainly parses the code in the parseStringValue method above

int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
    return value;
}
Copy the code

**${** * returns -1 if not found. This returns the current filename

while(startIndex ! = -1) {
    int endIndex = findPlaceholderEndIndex(result, startIndex);
    if(endIndex ! = -1) {
		String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
        String originalPlaceholder = placeholder;
        if (visitedPlaceholders == null) {
            visitedPlaceholders = new HashSet<>(4);
        }
        if(! visitedPlaceholders.add(originalPlaceholder)) {throw new IllegalArgumentException(
                "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
        }
        // Recursive invocation, parsing placeholders contained in the placeholder key.
        placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
        // Now obtain the value for the fully resolved key...String propVal = placeholderResolver.resolvePlaceholder(placeholder); . }}Copy the code

}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}

It then adds the acquired property to a HashSet to save it

  • placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
  • Application {USERNAME{PCNAME}} Application {USERNAME{PCNAME}}
  • So this is a recursive call to get the nested placeholder inside

The corresponding value of the attribute will then be obtained via the resolvePlaceholder method

if(propVal ! =null) {
    // Recursive invocation, parsing placeholders contained in the previously resolved placeholder value.
    propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
    result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
    if (logger.isTraceEnabled()) {
        logger.trace("Resolved placeholder '" + placeholder + "'");
    }
    ${${
    startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
Copy the code

When the proVal is not empty, the placeholder is replaced by a new call to the parseStringValue method to find that the property value contains the placeholder, and then the placeholder in the original file name is replaced

Then look again for **${**, if this time does not arrive, then it indicates that the placeholder in the file name has been completely replaced.

This completes file name resolution before loading the configuration file