說到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
方法以便找到這個獲取到的屬性值裏面也包含佔位符,而後就是對原來的文件名中的佔位符進行替換了
而後從新查找一次**${**,若是這個時候沒有到的,那麼就說明文件名中的佔位符已經徹底被替換調了.
至此就完成加載配置文件以前的文件名解析的工做了