上一節分析了父容器(ClasspathXmlApplicationContext)的加載,其加載的配置檔案可以在web.xml中配置,但是并沒有仔細的去研究父容器的建立的過程,那是因為父容器的建立與子容器的建立基本一緻。本節将分析ApplicationContext與BeanFactory的重新整理過程。
2.1 配置檔案的解析
context建立後,接下來就需要準備好配置檔案,以供後面解析配置做準備。
//(0) 配置和重新整理context
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
//底層使用了JDK提供的類名[email protected]+hashCode位址作為唯一id,在調用容器構造器的時候生成
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
//通過在web.xml中配置contextId來修改預設的id
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
//如果web.xml中未提供,生成與web相關的id
//一般情況下就是org.springframework.web.context.WebApplicationContext: + sc.getContextPath())
//目前這個id,我看見在建構容器關聯的BeanFactory的時候被使用到,用于設定BeanFactory的序列化id
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
//從web.xml中擷取配置檔案路徑
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
//設定配置檔案路徑,這個容器是實作了ConfigurableWebApplicationContext接口,還有PropertyResolver接口,會對這個配置路徑進行占位符的解析,目前能夠擷取到的占位符屬性為System.getProperties(),System.getEnv(),其他的ServletContext的屬性,ServletConfig的屬性都為存根對象,直接傳回null
wac.setConfigLocation(configLocationParam);
}
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
//替換建立ConfigurableEnvironment時設定的存根占位符
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
//調用ApplicationContextInitializer
customizeContext(sc, wac);
//(1)重新整理容器
wac.refresh();
}
上面給建立的context設定唯一的id之後,從web.xml中讀取了配置檔案的路徑,并将配置檔案路徑設定給context,看似是簡單的set方法,實際上spring做了其他的事情,那麼就是對配置檔案路徑進行占位符的解析,替換完占位符才會設定給context,解析完配置檔案路徑之後,會調用實作了ApplicationContextInitializer的初始化方法,最後就是重新整理容器。 這裡還有一個值得注意的是,在設定配置檔案路徑的過程中,為了解析占位符,spring會建立一個StandardServletEnvironment執行個體,這個執行個體具有提供屬性資源和解析占位符的能力,接下來,我們來看看spring是如果解析占位符的。
public void org.springframework.context.support.AbstractRefreshableConfigApplicationContext.setConfigLocations(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++) {
//(1)解析占位符
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
//(1)解析占位符,那解析後的占位符的屬性值,從哪裡找呢?是以這個時候我們需要建立一個環境執行個體,用于提供屬性值,由于目前context是實作了WebApplicationContext接口,繼而它的實作方法在AbstractRefreshableWebApplicationContext中,是以它所建立的環境變量當然會是與web相關的StandardServletEnvironment
protected ConfigurableEnvironment createEnvironment() {
return new StandardServletEnvironment();
}
我們來看看StandardServletEnvironment的類圖
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2csUTTq1UMnpnTzkkeYhnRzwEMW1mY1RzRapnTtxkb5ckYplTeMZTTINGMShUYfRHelRHLwEzX39GZhh2css2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xyayFWbyVGdhd3LcV2Zh1Wa9M3clN2byBXLzN3btg3Pn5GcuIjMyQDO1QTM0ADOwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
PropertyResolver(定義根據屬性key擷取屬性值,定義解析占位符功能)
ConfigurablePropertyResolver(定義擷取ConversionService的能力,用于進行屬性值的轉換,ConversionServic,提供轉換能力,ConverterRegistry,注冊轉換器)
Environment(定義擷取預設激活配置檔案profile的能力)
ConfigurableEnvironment(設定激活配置檔案,以及擷取配置檔案的能力)
AbstractEnvironment(實作了解析占位符的能力,持有PropertySourcesPropertyResolver(用于解析占位符),MutablePropertySources(提供屬性值,内部采用組合模式)執行個體對象)
StandardEnvironment(customizePropertySources)
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
//内部使用System.getProperty()
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
//内部使用System.getEnv()
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
StandardServletEnvironment(customizePropertySources)
protected void customizePropertySources(MutablePropertySources propertySources) {
//設定servletConfigInitParams存根,其實就是起占位符的能力
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
//servletContextInitParams
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
//jndi占位符jndiProperties
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
//這個就是上面那個方法
super.customizePropertySources(propertySources);
}
從上面的繼承結構我們大緻了解了StandardServletEnvironment具有提供屬性值和解析替換占位符的能力,那麼它的屬性資源從哪裡來呢?其中有部分屬性資源在構造它的執行個體的時候就會預設添加,具體代碼如下:
protected void org.springframework.web.context.support.StandardServletEnvironment.customizePropertySources(MutablePropertySources propertySources) {
//添加servletConfig存根對象,不提供屬性值,調用時傳回null(所謂存根對象,相當于占位符,後期會進行替換,為啥要占着茅坑不拉屎呢?你想象一下停車位就知道了,你要放個牌子,告訴别人這是我的,如果你不放就會被人霸占。)
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
//調用父類的customizePropertySources方法super.customizePropertySources(propertySources);這裡我将父類的方法内聯進來了
//添加系統屬性變量
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
//添加系統環境變量
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
以上通過MutablePropertySources添加了多個屬性源,當所需要的屬性源準備就緒之後就可以進行占位符的解析了
protected String resolvePath(String path) {
//解析占位符
return getEnvironment().resolveRequiredPlaceholders(path);
}
public String org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
//建立占位符解析幫助類,這個類主要用于占位符的解析,而目前執行個體是PropertySourcesPropertyResolver類的執行個體,這個類主要負責屬性值的擷取
if (this.strictHelper == null) {
this.strictHelper = createPlaceholderHelper(false);
}
//解析占位符,将解析後的占位符到屬性資源中擷取屬性值,然後替換之後傳回
return doResolvePlaceholders(text, this.strictHelper);
}
//解析占位符
protected String parseStringValue(
String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder result = new StringBuilder(strVal);
//擷取${開始位置
int startIndex = strVal.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
//擷取最終與${配對的},因為可能存在這樣的${${spring.profil.action}:dev}
//這裡的${應當比對:dev後面這個}
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
//不允許循環引用,否則一直遞歸,導緻Stack Overflow
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) {
//如果沒有擷取到占位符,那麼擷取預設值,比如${${spring.profil.action}:dev},預設值是dev
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 string value \"" + strVal + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return result.toString();
}
下面是解析占位符的序列圖
2.2 context的重新整理
public void org.springframework.context.support.AbstractApplicationContext.refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//(2)設定context啟動時間,設定激活标記,設定是否關閉标記,并且初始化資源,這裡會提供初始化屬性資源的機會,并對屬性資源的校驗機會
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//(3)建立context關聯的BeanFactory,這個建立的是DefaultListableBeanFactory,重新整理容器,加載配置檔案
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
。。。。。。
}
//(2)
protected void org.springframework.context.support.AbstractApplicationContext.prepareRefresh() {
//設定啟動時間
this.startupDate = System.currentTimeMillis();
//設定關閉flag
this.closed.set(false);
//設定激活flag
this.active.set(true);
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// Initialize any placeholder property sources in the context environment
//我們可以繼承這個類,然後設定自己的propertSource,自己重新設定配置檔案位置,甚至解析占位符。縱觀以前的運作流程,我們可以更改設定父容器的能力,id,環境等等資訊,甚至我們後面可以修改建立BeanFactory的方法等方式。
initPropertySources();
// Validate that all properties marked as required are resolvable
// see ConfigurablePropertyResolver#setRequiredProperties
//校驗一些必填屬性
getEnvironment().validateRequiredProperties();
// Allow for the collection of early ApplicationEvents,
// to be published once the multicaster is available...
//這裡可以設定餓漢事件,一旦事件廣播可用,就會觸發對應的事件
this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
}
//(3)這個方法内部調用了這個方法
protected final void org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory() throws BeansException {
//如果已經存在BeanFactory,那麼就進行銷毀,主要用于重新啟動容器的時候
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//建立BeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
//設定BeanFactory是否允許BeanDefinition的覆寫,是否允許循環依賴
customizeBeanFactory(beanFactory);
//(4)建立XmlBeanDefinitionReader,設定BeanFactory到XmlBeanDefinitionReader等,準備加載BeanDefinition
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
//(4)
protected void org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
//設定環境屬性,用于配置檔案中占位符的解析
beanDefinitionReader.setEnvironment(getEnvironment());
//設定配置資源加載器,由于context實作了ResourceLoader接口,context是有加載資源的能力的
beanDefinitionReader.setResourceLoader(this);
//解析器,用于校驗配置檔案的正确性
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
//鈎子函數,提供給子類實作
initBeanDefinitionReader(beanDefinitionReader);
//使用reader加載解析配置檔案
loadBeanDefinitions(beanDefinitionReader);
}
上面這段代碼上面的注釋非常明确,這裡不多贅述,主要是看看DefaultListableBeanFactory,context應用了這個BeanFactory的執行個體,那麼它具有什麼樣的能力,我們來看下它的類圖
AliasRegistry(提供别名注冊,移除的功能)
BeanDefinitionRegistry(提供BeanDefinition注冊能力)
SimpleAliasRegistry(提供别名注冊,移除的實作能力)
DefaultSingletonBeanRegistry(提供單例bean的注冊,擷取)
FactoryBeanRegistrySupport(定義如何擷取工廠bean與注冊工廠bean到緩存中)
AbstractBeanFactory(主要提供擷取bean的模闆方法(createBean))
AbstractAutowireCapableBeanFactory(具有自動裝配能力以及BeanFactoryPostProccess擴充能力)
SingletonBeanRegistry(注冊單例bean,擷取單例bean)
BeanFactory(定義擷取bean的能力)
HierarchicalBeanFactory(定義擷取父BeanFactory的能力)
ConfigurableBeanFactory(提供配置功能,定義擷取轉換器的能力)
AutowireCapableBeanFactory(定義自動裝配的功能)
ListableBeanFactory(定義可枚舉bean的能力)
ConfigurableListableBeanFactory(定義了提前初始化非懶加載bean)
DefaultListableBeanFactory(提供具體的枚舉bean的能力)