前言
上篇博客我们简单介绍了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函数后期介绍