说到Spring启动流程,仍是要在加载配置主文件开始.html
我这里有一个小Demo,项目的结构是这样的java
而它的配置文件也是很简单的spring
<?xml version="1.0" encoding="UTF-8"?>
<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>
复制代码
用于测试的类数组
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());
}
}
复制代码
这一切的开始都源自于ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:application.xml");
,markdown
想必用过Spring的小伙伴对于这行代码已经很熟悉了,这行代码是加载Spring配置文件的代码。它是读取的classpath
路径下面的文件,所在上面代码中的classpath前缀写不写均可
app
当跟着断点走到了ClassPathXmlApplicationContext
类中,最后他们会走到这样的一个构造方法里面去ide
public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
复制代码
注:当你看到有的方法里面调用了super时,不要跳过,由于里面可能会进行建立一些对象,后面会用到,最好是看一下测试
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}
复制代码
当一路点着super
最后来到一个名叫AbstractApplicationContext
的类时,这里会建立出一些对象,先了解下,后面可能会用到ui
还有一些其它的对象,有兴趣的能够看下this
接下在看到 this()
里面时,它会对这个资源解析器进行赋值
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
复制代码
而在这个getResourcePatternResolver()
方法里面是建立了一个PathMatchingResourcePatternResolver
对象.
PathMatchingResourcePatternResolver主要用途就是经过给定的文件路径或者是经过Ant风格的路径来查找到相应的资源
在AbstractXmlApplicationContext
类里面有一个名叫validating
,这个我以为也是一个要看的地方,这个是表示是否要使用XML验证的一个标志
private boolean validating = true;
/** * Set whether to use XML validation. Default is {@code true}. */
public void setValidating(boolean validating) {
this.validating = validating;
}
复制代码
在super里面主要是作了对于一些变量进行了初始化,这些变量在后面会用到,因此你们要看下.
此次以前先看下ClassPathXmlApplicationContext
的类关系图,由于里面不少的方法是直接使用的父类里面的方法.
进入setConfigLocations
方法,其实这里已经来到了AbstractRefreshableApplicationContext
类中
/** * Set the config locations for this application context. * <p>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;
}
}
复制代码
这个方法会将配置文件的路径传入,而且将他们从新放到configLocations
里面,
这里会调用一个名叫解析路径的方法resolvePath()
,为啥会有这么一个东西?
/** * 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);
}
复制代码
在有的配置文件名字写成application${username},这种名字里面带有占位符的文件名,这里能够将这个占位符给替换掉.
正如注释中写的那样:解析给定的路径,若有必要,用相应的环境属性值替换占位符。
而这里的getEnvironment()
方法就是建立了一个标准环境对象类,里面包含了系统属性
和系统环境属性
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}
复制代码
在下面的属性里面看到了一个SESSIONNAME
的属性,例如:有一个配置文件的名子是这样写的application${SESSIONNAME}
当程序对这个文件名解析后就会变成applicationConsole
,上面说的解析路大概也就是这个意思.
来到resolveRequiredPlaceholders
方法后,传进来了配置文件的文件名,要对其进行解析
这里为了方便展现将ClassPathXmlApplicationContext里面的参数改为application{USERNAME{USERDOMAIN_ROAMINGPROFILE}}.xml
这里的属性在其它电脑上可能不存在,找一个大家电脑有的参数就好
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
this.strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, this.strictHelper);
}
复制代码
小提示:只要是看到do开头的方法,就是开始要进行真正的操做了
进入方法首先是判断有没有一个占位符帮助器的对象,没有的话就要建立出来,这里没啥好说的,就是在这个方法里面建立了一个对象.自已能够看下,这里主要是看doResolvePlaceholders()
方法.
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
复制代码
这里就直接调用了这个帮助器里面的replacePlaceholders
方法,这个方法就直接看parseStringValue
方法就行了
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, null);
}
复制代码
下面这个方法就开始占位符的替换了,
再此以前咱们先来看下建立占位符帮助器的方法,上面一开始说自已看就好,这里我仍是说下吧(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);
}
复制代码
还记得上面我说过在解析文件名的时候系统能够替换** {}里面的而不是##**里面的东西呢?缘由就在它建立占位符帮助器的时的构造传参里面.
在建立这个对象的时候,构造参数里面有一个placeholderPrefix
和placeholderSuffix
而它们分别对应**${和}**
在看下面parseStringValue
方法的时候,它第一行代码里面的this.placeholderPrefix的值就是**${**
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();
}
复制代码
这个目录里面主要是对上面parseStringValue
方法里面的代码进行分段的解析
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
}
复制代码
上面这段代码是在获取到这个文件名里面第一次出现**${**的位置,若是没有找到就会返回-1,这样就直接的返回当前的文件名了
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);
...
}
}
复制代码
在这个While里面先经过findPlaceholderEndIndex
方法来找到这个占位符所对应的结束符(}),找到它的位置,若是找到了就进入下面的if里面.
而后它会将这个获取到的属性加入到一个HashSet里面将其保存起来
而后会经过resolvePlaceholder
方法将属性所对应的值获取到
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());
}
复制代码
当获取到的proVal结果不是空的时候就要开始对占位符进行替换的工做了,这里要从新的调用一次parseStringValue
方法以便找到这个获取到的属性值里面也包含占位符,而后就是对原来的文件名中的占位符进行替换了
而后从新查找一次**${**,若是这个时候没有到的,那么就说明文件名中的占位符已经彻底被替换调了.
至此就完成加载配置文件以前的文件名解析的工做了