Spring IOC原始碼分析之-重新整理前的準備工作
目錄
- ClassPathXmlApplicationContext的註冊方式
- 載入父子容器
- 配置路徑解析
- 容器重新整理
- 重新整理容器之重新整理預處理
- ClassPathXmlApplicationContext的註冊方式
ClassPathXmlApplicationContext的註冊方式
原始碼分析基於Spring4.3
從ClassPathXmlApplicationContext
入口,最終都會呼叫到
/* * 使用給定父級建立新的ClassPathXmlApplicationContext,從給定的XML檔案載入定義資訊。 * 載入所有的bean 定義資訊並且建立所有的單例 * 或者,在進一步配置上下文之後手動呼叫重新整理。 */ public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
上述註釋的解釋如是說:在容器的啟動過程中,初始化過程中所有的bean都是單例存在的
自動重新整理
ApplicationContext context = new ClassPathXmlApplicationContext("xxx.xml");
就等同於
手動重新整理
ApplicationContext context = new ClassPathXmlApplicationContext(); context.register("xxx.xml"); context.refresh();
上述一共有三條鏈路,下面來一一分析
載入父子容器
- 首先是載入並初始化父容器的方法
- 第一個出場的是
ClassPathXmlApplicationContext
,它是一個獨立的應用程式上下文,從類路徑獲取上下文定義檔案,能夠將普通路徑解析為包含包路徑的類路徑資源名稱。它可以支援Ant-Style(路徑匹配原則),它是一站式應用程式的上下文,考慮使用GenericApplicationContext類結合XmlBeanDefinitionReader來設定更靈活的上下文配置。
Ant-Style 路徑匹配原則,例如 "mypackages/application-context.xml" 可以用"mypackages/*-context.xml" 來替換。
⚠️注意: 如果有多個上下文配置,那麼之後的bean定義將覆蓋之前載入的檔案。這可以用來通過額外的XML檔案故意覆蓋某些bean定義
隨後不緊不慢走過來的不是一個完整的somebody,
AbstractXmlApplicationContext
, 它是為了方便ApplicationContext的實現而出現的(抽象類一個很重要的思想就是適配)。AbstractXmlApplicationContext 的最主要作用就是通過建立一個XML閱讀器解析ClassPathXmlApplicationContext 註冊的配置檔案。它有兩個最主要的方法 loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 和 loadBeanDefinitions(XmlBeanDefinitionReader reader)- 下一個緩緩出場的是
AbstractRefreshableConfigApplicationContext
,它就像是中間人的角色,並不作多少工作,很像古代丞相的奏摺要呈遞給皇上,它的作用就相當於是拿奏摺的角色。它用作XML應用程式上下文實現的基類,例如ClassPathXmlApplicationContext、FileSystemXmlApplicationContext和XmlWebApplicationContext - 當老闆的一般都比較聽小祕的,那麼
AbstractRefreshableApplicationContext
就扮演了小祕的角色,它是ApplicationContext的基類,支援多次呼叫refresh()方法,每次都會建立一個新的內部bean factory例項。繼承 AbstractRefreshableApplicationContext 需要唯一實現的方法就是loadBeanDefinitions,在每一次呼叫重新整理方法的時候。一個具體的實現是載入bean定義資訊的DefaultListableBeanFactory。 但是隻有小祕給老闆遞交請辭不行,中間還要有技術leader 來縱覽大局,向上與老闆探討公司發展計劃,在下領導新人做專案打硬仗(這種男人真的很有魅力哈哈哈),但是技術leader也不能幹完所有的工作,他還需要交給手下的程式設計師去幫他完成具體的工作,程式設計師接到一項工作,看看有沒有可複用的專案和開源類庫,發現有可用的,直接把"引用"連結過去就可以了。這就是容器的初始化工作,但是這一步的流程還沒有結束,你還得時刻記住你是給boss幹活的。
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
// 交給其他程式設計師去完成的工作
this();
// 明確自己的老闆是誰
setParent(parent);
}
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
// 返回 ResourcePatternResolver 去解析資源例項中的匹配模式,預設的是 PathMatchingResourcePatternResolver 支援 Ant-Style 模式。
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
// 此時的resourceLoader 就是ClassPathXmlApplicationContext 物件。
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
你需要一些程式設計師幫你做具體的編碼工作,也需要明確你是公司的員工,需要聽從老闆的,所以你需要明確老闆是誰
@Override
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
但是這個時候老闆出差了,不在了(因為傳過來的parent 是 null),所以你需要自己做一些decision。至此,第一條線路就分析完成了。
配置路徑解析
- 第二條線路,
ApplicationContext
中的 setConfigLocations(configLocations)
// 引數傳過來的是可變引數,可變引數是一個數組,也就是說,你可以傳遞多個配置檔案,用","分隔起來。
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
// configlocations 是一個可為空的String陣列,可以為null,為null可以進行手動註冊。
this.configLocations = new String[locations.length];
// 解析陣列中的每一個配置檔案的路徑。
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
// 預設是直接建立了一個 ClassPathXmlApplicationContext 的無引數的建構函式,採用手動註冊的方式。
else {
this.configLocations = null;
}
}
關鍵點:路徑解析方法 : AbstractRefreshableConfigApplicationContext
中的 resolvePath(locations[i]).trim(); 來看看是如何進行路徑解析的
// 解析給定的路徑,必要時用相應的環境屬性值替換佔位符。應用於路徑配置。
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
涉及兩個方法,AbstractRefreshableConfigApplicationContext 中的getEnvironment() 和 validateRequiredProperties(),先來看第一個
getEnvironment()
// 以配置的形式返回此應用程式上下文的Environment,來進一步自定義 // 如果沒有指定,則通過初始化預設的環境。 @Override public ConfigurableEnvironment getEnvironment() { if (this.environment == null) { // 使用預設的環境配置 this.environment = createEnvironment(); } return this.environment; }
下面來看一下createEnvironment()如何初始化預設的環境:
// 建立並返回一個 StandardEnvironment,子類重寫這個方法為了提供 // 一個自定義的 ConfigurableEnvironment 實現。 protected ConfigurableEnvironment createEnvironment() { // StandardEnvironment 繼承AbstractEnvironment,而AbstractEnvironment // 實現了ConfigurableEnvironment return new StandardEnvironment(); }
其實很簡單,也只是new 了一個StandardEnvironment() 的構造器而已。StandardEnvironment是什麼?非web應用程式的Environment 的標準實現。他實現了AbstractEnvironment 抽象類,下面是具體的繼承樹:
StandardEnvironment是AbstractEnvironment的具體實現,而AbstractEnvironment又是繼承了ConfigurableEnvironment介面,提供了某些方法的具體實現,ConnfigurableEnvironment 繼承了Environment,而Environment 和 ConfigurablePropertyResolver 同時繼承了PropertyResolver
下面來看一下StandardEnvironment() 的原始碼:
public class StandardEnvironment extends AbstractEnvironment {
// 系統屬性資源名稱
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
// JVM系統屬性資源名:
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
//為標準的Java 環境 自定義合適的屬性檔案
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
}
現在讀者就會產生疑問,不是說new出來一個標準的StandardEnvironment 實現嗎,但是StandardEnvironment並沒有預設的構造方法啊?這是什麼回事呢?
其實StandardEnvironment 的構造方法是 AbstractEnvironment:
public AbstractEnvironment() {
// 實現自定義屬性資源的方法,也就是StandardEnvironment中customizePropertySources()
customizePropertySources(this.propertySources);
if (logger.isDebugEnabled()) {
logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);
}
}
上述的`customizePropertySources` 由`StandardEnvironment` 來實現,具體如下
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
由於容器在剛起步的時候 propertySources 是null,所以新增完系統環境(systemEnvironment)和系統屬性(systemProperties) 之後,會變成下圖所示
如何獲取系統屬性和如何獲取系統環境沒有往下跟,有興趣的讀者可以繼續沿用。
大致截一個圖,裡面大概的屬性是這樣
systemProperties
systemEnvironment
- 另外一個是 resolveRequiredPlaceholders,它是由
PropertyResolver
超頂級介面定義的方法
// 在給定的text 引數中解析${} 佔位符,將其替換為getProperty 解析的相應屬性值。
// 沒有預設值的無法解析的佔位符將導致丟擲IllegalArgumentException。
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
- 由
AbstractPropertyResolver
子類來實現,且看AbstractPropertyResolver
的繼承樹
-
具體實現的方法如下:
// 傳遞進來的文字就是解析過的 配置檔案 SimpleName @Override public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { if (this.strictHelper == null) { this.strictHelper = createPlaceholderHelper(false); } return doResolvePlaceholders(text, this.strictHelper); } // 呼叫createPlaceholderHelper private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) { return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix, this.valueSeparator, ignoreUnresolvablePlaceholders); } ----------------------------PropertyPlaceholderHelper------------------------------- // PropertyPlaceholderHelper載入的時候會把下面的特殊字元放進去 static { wellKnownSimplePrefixes.put("}", "{"); wellKnownSimplePrefixes.put("]", "["); wellKnownSimplePrefixes.put(")", "("); } /* 建立一個新的 PropertyPlaceholderHelper 使用提供的字首 和 字尾 * 引數解釋:placeholderPrefix 佔位符開頭的字首 * placeholderSuffix 佔位符結尾的字尾 * valueSeparator 佔位符變數和關聯的預設值 之間的分隔符 * ignoreUnresolvablePlaceholders 指示是否應忽略不可解析的佔位符。 */ public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, @Nullable String valueSeparator, boolean ignoreUnresolvablePlaceholders) { Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null"); Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null"); this.placeholderPrefix = placeholderPrefix; this.placeholderSuffix = placeholderSuffix; String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix); if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) { this.simplePrefix = simplePrefixForSuffix; } else { this.simplePrefix = this.placeholderPrefix; } this.valueSeparator = valueSeparator; this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; }
解析完成佔位符之後,需要做真正的解析,呼叫
AbstractPropertyResolver
中的doResolvePlaceholders 方法。private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) { return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() { @Override public String resolvePlaceholder(String placeholderName) { return getPropertyAsRawString(placeholderName); } }); }
PlaceholderResolver
是 PropertyPlaceholderHelper類的內部類,這是一種匿名內部類的寫法,它真正呼叫的就是PropertyPlaceholderHelper
中的 replacePlaceholders 方法,具體如下:// 將格式為 ${name} 的佔位符替換為從提供 PlaceholderResolver 返回的值。 public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) { Assert.notNull(value, "'value' must not be null"); return parseStringValue(value, placeholderResolver, new HashSet<String>()); } protected String parseStringValue( String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) { StringBuilder result = new StringBuilder(value); int startIndex = value.indexOf(this.placeholderPrefix); // 判斷指定的佔位符有無 ${ 存在,沒有的話直接返回 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.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(); }
直白一點,上述過程就是用來判斷有沒有 ${ 這個佔位符,如果有的話就進入下面的判斷邏輯,把${}
中的值替換為 PlaceholderResolver 返回的值,如果沒有的話,就直接返回。
- 由
容器重新整理
在經過上述的準備工作完成後,接下來就是整個IOC,DI和AOP的核心步驟了,也是Spring框架的靈魂。由於原始碼太多,設計範圍太廣,本篇只分析重新整理預處理應該做的事:我們都知道,無論你載入的是哪一種上下文環境,最終都會呼叫 AbstractApplicationContext
的refresh()方法,此方法是一切載入、解析、註冊、銷燬的核心方法,採用了工廠的設計思想。
// 完成IoC容器的建立及初始化工作
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1: 重新整理前的準備工作。
prepareRefresh();
// 告訴子類重新整理內部bean 工廠。
// 2:建立IoC容器(DefaultListableBeanFactory),載入解析XML檔案(最終儲存到Document物件中)
// 讀取Document物件,並完成BeanDefinition的載入和註冊工作
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3: 對IoC容器進行一些預處理(設定一些公共屬性)
prepareBeanFactory(beanFactory);
try {
// 4: 允許在上下文子類中對bean工廠進行後處理。
postProcessBeanFactory(beanFactory);
// 5: 呼叫BeanFactoryPostProcessor後置處理器對BeanDefinition處理
invokeBeanFactoryPostProcessors(beanFactory);
// 6: 註冊BeanPostProcessor後置處理器
registerBeanPostProcessors(beanFactory);
// 7: 初始化一些訊息源(比如處理國際化的i18n等訊息源)
initMessageSource();
// 8: 初始化應用事件多播器
initApplicationEventMulticaster();
// 9: 初始化一些特殊的bean
onRefresh();
// 10: 註冊一些監聽器
registerListeners();
// 11: 例項化剩餘的單例bean(非懶載入方式)
// 注意事項:Bean的IoC、DI和AOP都是發生在此步驟
finishBeanFactoryInitialization(beanFactory);
// 12: 完成重新整理時,需要釋出對應的事件
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 銷燬已經建立的單例避免佔用資源
destroyBeans();
// 重置'active' 標籤。
cancelRefresh(ex);
// 傳播異常給呼叫者
throw ex;
}
finally {
// 重置Spring核心中的常見內省快取,因為我們可能不再需要單例bean的元資料了...
resetCommonCaches();
}
}
}
重新整理容器之重新整理預處理
此步驟的主要作用在於:準備重新整理的上下文,設定啟動的時間和active的標誌作為扮演屬性資源初始化的角色。
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// 初始化environment 上下文中的佔位符屬性資源
initPropertySources();
// 驗證標記為必需的所有屬性是否可解析
getEnvironment().validateRequiredProperties();
// 允許收集早期的ApplicationEvents
this.earlyApplicationEvents = new LinkedHashSet<>();
}
這裡面有兩處程式碼需要說明:initPropertySources
這個方法是需要子類進行實現的,預設是不會做任何事情的;getEnvironment()
這個方法由於上述的原始碼分析過程中,已經預設建立了 createEnvironment,所以這段程式碼是直接返回的
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
下面只剩下了validateRequiredProperties()的分析,不著急,看原始碼不能著急,要懷著這個世界很美好的心情去看。
首先在 ConfigurablePropertyResolver
介面中定義了 validateRequiredProperties 方法
// 驗證每一個被setRequiredProperties 設定的屬性存在並且解析非空值,會丟擲
// MissingRequiredPropertiesException 異常如果任何一個需要的屬性沒有被解析。
void validateRequiredProperties() throws MissingRequiredPropertiesException;
在抽象子類AbstractPropertyResolver 中被重寫
@Override
public void validateRequiredProperties() {
// 屬性找不到丟擲異常的物件
MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
for (String key : this.requiredProperties) {
if (this.getProperty(key) == null) {
ex.addMissingRequiredProperty(key);
}
}
if (!ex.getMissingRequiredProperties().isEmpty()) {
throw ex;
}
}
因為在我們的原始碼分析中,沒有看到任何操作是在對 requiredProperties 進行新增操作,也就是如下:
@Override
public void setRequiredProperties(String... requiredProperties) {
if (requiredProperties != null) {
for (String key : requiredProperties) {
this.requiredProperties.add(key);
}
}
}
所以,此時的 requiredProperties 這個set集合是null, 也就不存在沒有解析的元素了。
本篇到此就結束了,下一篇文章會進行原始碼分析的下一個步驟: 建立IOC容器以及Bean的解