源碼分析基於Spring4.3程序員
從ClassPathXmlApplicationContext
入口,最終都會調用到web
/* * 使用給定父級建立新的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");就等同於app
手動刷新
框架ApplicationContext context = new ClassPathXmlApplicationContext(); context.register("xxx.xml"); context.refresh();
上述一共有三條鏈路,下面來一一分析ide
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和XmlWebApplicationContextAbstractRefreshableApplicationContext
就扮演了小祕的角色,它是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
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的解析。