前言
上篇博客我們簡單介紹了ApplicationContext,說實話,講得太糙了,自己都看不下去了。所以打算在本文和後面的文章以稍微詳細的說明來彌補之前的不足。本文將以debug ClassPathXmlApplicationContext的方式一步一步去了解Application。
1. 概述
首先讓我們來看個spring的測試用例:
private static final String PATH = "/org/springframework/context/support/";
private static final String CONTEXT_A = "test/contextA.xml";
private static final String CONTEXT_B = "test/contextB.xml";
private static final String CONTEXT_C = "test/contextC.xml";
private static final String FQ_CONTEXT_A = PATH + CONTEXT_A;
private static final String FQ_CONTEXT_B = PATH + CONTEXT_B;
private static final String FQ_CONTEXT_C = PATH + CONTEXT_C;
@Test
public void testMultipleConfigLocations() {
// 根據多個xml路徑實例化beans
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
FQ_CONTEXT_B, FQ_CONTEXT_C, FQ_CONTEXT_A);
// 斷言,BeanFacoty(containsBean是ClassPathXmlApplication的糾結父類BeanFantocty的方法)裏面是否包含傳入參數的方法。
assertTrue(ctx.containsBean("service"));
assertTrue(ctx.containsBean("logicOne"));
assertTrue(ctx.containsBean("logicTwo"));
// 通過getBean獲取相應的實例化bean
// re-refresh (after construction refresh)
Service service = (Service) ctx.getBean("service");
ctx.refresh();
assertTrue(service.isProperlyDestroyed());
// regular close call
service = (Service) ctx.getBean("service");
ctx.close();
assertTrue(service.isProperlyDestroyed());
// re-activating and re-closing the context (SPR-13425)
ctx.refresh();
service = (Service) ctx.getBean("service");
ctx.close();
assertTrue(service.isProperlyDestroyed());
}
上面是一個ClassPathXmlApplicationContext的應用,通過xml的路徑,獲取xml下的bean配置,從而獲取實例化bean。
1.1 初識ClassPathXmlApplicationContext
下面看看Spring中開發者對ClassPathXmlApplicationContext的講解
/**
* Standalone XML application context, taking the context definition files
* from the class path, interpreting plain paths as class path resource names
* that include the package path (e.g. "mypackage/myresource.txt"). Useful for
* test harnesses as well as for application contexts embedded within JARs.
* 獨立XML應用程序上下文,從類路徑中獲取上下文定義文件,將純路徑解釋爲包含程序包路徑的類路徑資源名稱(例如“mypackage / myresource.txt”)。對測試工具以及嵌入JAR的應用程序上下文非常有用。
* <p>The config location defaults can be overridden via {@link #getConfigLocations},
* Config locations can either denote concrete files like "/myfiles/context.xml"
* or Ant-style patterns like "/myfiles/*-context.xml" (see the
* {@link org.springframework.util.AntPathMatcher} javadoc for pattern details).
*
* <p>Note: In case of multiple config locations, later bean definitions will
* override ones defined in earlier loaded files. This can be leveraged to
* deliberately override certain bean definitions via an extra XML file.
*
* <p><b>This is a simple, one-stop shop convenience ApplicationContext.
* Consider using the {@link GenericApplicationContext} class in combination
* with an {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}
* for more flexible context setup.</b>
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see #getResource
* @see #getResourceByPath
* @see GenericApplicationContext
*/
下面是ClassPathXmlApplicationContext類圖!
由圖可知,ClassPathXmlApplicationContext繼承關係爲:
@startuml
ApplicationContext<|—AbstactApplicationContext<|—AbstractRefreshableApplicationContext<—AbstractRefreshableConfigApplicationContext<|—AbstractXmlApplicationContext<|–ClassPathXmlApplicationContext
@enduml
下圖爲ClassPathXmlApplicationContext所包含的方法:
又上圖可知,ClassPathXmlApplicationContext幾乎全是構造方法的重載(Overload)
/**
* 不難看出,其造函數主要分爲兩類:
* 1.指定xml文件配置路徑,不指定需要獲取的bean實例化對象。
* 2.指定xml文件配置路徑,指定需要獲取的bean實例化對象。
* 分別爲一下兩種方法。
*/
/**
* Create a new ClassPathXmlApplicationContext with the given parent,
* loading the definitions from the given XML files.
* @param configLocations array of resource locations
* @param refresh whether to automatically refresh the context,
* loading all bean definitions and creating all singletons.
* Alternatively, call refresh manually after further configuring the context.
* @param parent the parent context
* @throws BeansException if context creation failed
* @see #refresh()
*/
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
/**
* Create a new ClassPathXmlApplicationContext with the given parent,
* loading the definitions from the given XML files and automatically
* refreshing the context.
* @param paths array of relative (or absolute) paths within the class path
* @param clazz the class to load resources with (basis for the given paths)
* @param parent the parent context
* @throws BeansException if context creation failed
* @see org.springframework.core.io.ClassPathResource#ClassPathResource(String, Class)
* @see org.springframework.context.support.GenericApplicationContext
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
*/
public ClassPathXmlApplicationContext(String[] paths, Class<?> clazz, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
Assert.notNull(paths, "Path array must not be null");
Assert.notNull(clazz, "Class argument must not be null");
this.configResources = new Resource[paths.length];
for (int i = 0; i < paths.length; i++) {
this.configResources[i] = new ClassPathResource(paths[i], clazz);
}
refresh();
}
2. 一起學習源碼—debug spring test
下面將debug org.springframework.context.support.ClassPathXmlApplicationContextTests 中的testConfigLocationPattern,以此瞭解ApplicationContext中的ClassPathXmlApplicationContext的初始化過程。
private static final String PATH = "/org/springframework/context/support/";
private static final String CONTEXT_WILDCARD = PATH + "test/context*.xml";
@Test
public void testConfigLocationPattern() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(CONTEXT_WILDCARD);
assertTrue(ctx.containsBean("service"));
assertTrue(ctx.containsBean("logicOne"));
assertTrue(ctx.containsBean("logicTwo"));
Service service = (Service) ctx.getBean("service");
ctx.close();
assertTrue(service.isProperlyDestroyed());
}
可知,此處是一個通過通配符獲取test目錄下所有已context打頭的所有xml配置文件,以此初始化ClassPathXmlApplicationContext,從而實例化beans。
以下一段只是覺得搞笑,當做註釋吧!
哈哈,太好玩了。當我進入到
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(CONTEXT_WILDCARD);
這個函數時,沒想到第一步居然是跳到了AbstractApplicationContext下的一段靜態代碼塊
static {
// Eagerly load the ContextClosedEvent class to avoid weird classloader issues
// on application shutdown in WebLogic 8.1. (Reported by Dustin Woods.)
ContextClosedEvent.class.getName();
}
注意看註釋,他說,這麼急切的加載ContextClosedEvent類,是爲了避免WebLogic 8.1中在關閉應用程序的時候出現奇怪的類加載器問題。
~QAQ~ 莫名的喜感
然後進入到ClassPathXmlApplicationContext的構造函數中,最終會調用以下這個構造函數
public ClassPathXmlApplicationContext(
String[] configLocations,
boolean refresh,
@Nullable ApplicationContext parent)throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
(1)Super(parent)—>首先會去加載父類的構造方法;
org/springframework/context/support/AbstractApplicationContext.java
/**
* Create a new AbstractApplicationContext with the given parent context.
* @param parent the parent context
*/
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}
//---------------------------------------------------------------------
// Implementation of ConfigurableApplicationContext interface
//---------------------------------------------------------------------
/**
* Set the parent of this application context.
* <p>The parent {@linkplain ApplicationContext#getEnvironment() environment} is
* {@linkplain ConfigurableEnvironment#merge(ConfigurableEnvironment) merged} with
* this (child) application context environment if the parent is non-{@code null} and
* its environment is an instance of {@link ConfigurableEnvironment}.
* @see ConfigurableEnvironment#merge(ConfigurableEnvironment)
*/
@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);
}
}
}
已知這裏通過傳進來的ApplicationContext獲取配置環境,但是顯然,我們調用的ClassPathXmlApplicationContext的構造方法傳入參數爲null。
(2) setConfigLocations(configLocations);——設置spring的配置文件
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;
}
}
易知,這裏根據傳入的xml路徑,去解析beans路徑。
這裏通過resolvePath去獲取一個StandardEnvironment環境的。
最後調用的是org.springframework.util.PropertyPlaceholderHelper 裏的parseStringValue方法.—-這裏會去解析路徑中的${}就不太懂了。。。
org.springframework.util.PropertyPlaceholderHelper
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();
}
(3)refresh();——//調用父類的refresh函數,進行一系列初始化
/**
* Load or refresh the persistent representation of the configuration,
* which might an XML file, properties file, or relational database schema.
* <p>As this is a startup method, it should destroy already created singletons
* if it fails, to avoid dangling resources. In other words, after invocation
* of that method, either all or no singletons at all should be instantiated.
* @throws BeansException if the bean factory could not be initialized
* @throws IllegalStateException if already initialized and multiple refresh
* attempts are not supported
*/
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
關於refresh函數裏面的一些具體函數,後面的博客將逐一介紹。
注:
- parseStringValue 還不知道做什麼的
- refresh函數後期介紹