一、前言
在之前我们已经对spring的生命周期、AOP、事物等源码有了大体的分析,其中在对源码分析中大家有看到过refresh方法,Spring容器创建之后,会调用它的refresh方法,refresh的时候会做很多事情:比如完成配置类的解析、各种BeanFactoryPostProcessor和BeanPostProcessor的注册、国际化配置的初始化、web内置容器的构造等等。
接下来我们以ClassPathXmlApplicationContext这容器入手看整个refresh方法到底做了哪些事情。以及分析Spring有哪些一些拓展点我们是可以进行拓展的。
二、refresh源码分析
首先先看以下的这段代码:
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("person.xml");
Person bean = ac.getBean(Person.class);
Person bean2 = ac.getBean(Person.class);
}
接下来我们就对这个ClassPathXmlApplicationContext的构造函数源码进行分析。
2.1 ClassPathXmlApplicationContext源码分析
首先我们看到ClassPathXmlApplicationContext构造函数就为我们做了几件事情:
- 调用父类的构造函数完成一些资源的初始化;
- 解析配置文件的路径,例如:applicationContext-${user.name}.xml这种配置文件名称,并且保存到configLocations中,为下一步的解析bean定义做准备;
- 调用refresh方法;
/**
* 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 {
/**
* 1.调用父类构造函数完成一些资源的初始化:
* 1.1 初始化默认的资源文件解析器,默认为:PathMatchingResourcePatternResolver.
* 1.2 初始化默认的类加载器.
*/
super(parent);
// 2.解析配置文件的路径,例如:applicationContext-${user.name}.xml这种配置文件名称,并且保存到configLocations中,为下一步的解析bean定义做准备
setConfigLocations(configLocations);
// 3.如果需要刷新容器
if (refresh) {
// 执行刷新容器的操作
refresh();
}
}
2.1.1父类构造方法-super(parent)源码分析
我们点击super(parent)一直往里面跟可以看到对应的构造方法实现:
/**
* Create a new AbstractXmlApplicationContext with the given parent context.
* @param parent the parent context
*/
// AbstractXmlApplicationContext
public AbstractXmlApplicationContext(@Nullable ApplicationContext parent) {
super(parent);
}
/**
* Create a new AbstractRefreshableConfigApplicationContext with the given parent context.
* @param parent the parent context
*/
// AbstractRefreshableConfigApplicationContext
public AbstractRefreshableConfigApplicationContext(@Nullable ApplicationContext parent) {
super(parent);
}
/**
* Create a new AbstractRefreshableApplicationContext with the given parent context.
* @param parent the parent context
*/
// AbstractRefreshableApplicationContext
public AbstractRefreshableApplicationContext(@Nullable ApplicationContext parent) {
super(parent);
}
/**
* Create a new AbstractApplicationContext with the given parent context.
* @param parent the parent context
*/
// AbstractApplicationContext
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}
// AbstractApplicationContext
/**
* Create a new AbstractApplicationContext with no parent.
*/
public AbstractApplicationContext() {
// 初始化一个默认的资源文件解析器对象,默认的类型为:PathMatchingResourcePatternResolver
this.resourcePatternResolver = getResourcePatternResolver();
}
其实一直往里面跟,他调用父类的构造方法主要是完成一些调用父类构造函数完成一些资源的初始化,其中包括:初始化默认的资源文件解析器,默认为:PathMatchingResourcePatternResolver和初始化默认的类加载器。
2.2 setConfigLocations源码分析
解析配置文件的路径,例如:applicationContext-${user.name}.xml这种配置文件名称,并且保存到configLocations中,为下一步的解析bean定义做准备;我们知道我们的配置文件路径可以是一些spel表达式,这一步主要是解析配置文件中的一些占位符;
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++) {
// 解析给定的配置文件的路径,例如:spring-${user.name}.xml会被解析为spring-wangbin33.xml
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}
然后我们接着往下看resolvePath的源码,看他具体是怎么进行解析的。
/**
* Resolve the given path, replacing placeholders with corresponding
* environment property values if necessary. Applied to config locations.
* @param path the original file path
* @return the resolved file path
* @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)
*/
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
我们可以看到这进行解析之前他先调用getEnvironment去获取一个环境对象,我们去看他怎么获取一个环境对象;
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
// StandardEnvironment
this.environment = createEnvironment();
}
return this.environment;
}
/**
* Create and return a new {@link StandardEnvironment}.
* <p>Subclasses may override this method in order to supply
* a custom {@link ConfigurableEnvironment} implementation.
*/
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}
看到这里我们就知道了我们的环境对象默认就是StandardEnvironment类型的,接下来我们看这个环境对象是做了哪些事情。
public class StandardEnvironment extends AbstractEnvironment {
/** System environment property source name: {@value}. */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
/** JVM system properties property source name: {@value}. */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
/**
* Customize the set of property sources with those appropriate for any standard
* Java environment:
* <ul>
* <li>{@value #SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME}
* <li>{@value #SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME}
* </ul>
* <p>Properties present in {@value #SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME} will
* take precedence over those in {@value #SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME}.
* @see AbstractEnvironment#customizePropertySources(MutablePropertySources)
* @see #getSystemProperties()
* @see #getSystemEnvironment()
*
* 初始化SystemProperties和SystemEnviroment
*/
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
/**
* System property that instructs Spring to ignore system environment variables,
* i.e. to never attempt to retrieve such a variable via {@link System#getenv()}.
* <p>The default is "false", falling back to system environment variable checks if a
* Spring environment property (e.g. a placeholder in a configuration String) isn't
* resolvable otherwise. Consider switching this flag to "true" if you experience
* log warnings from {@code getenv} calls coming from Spring, e.g. on WebSphere
* with strict SecurityManager settings and AccessControlExceptions warnings.
* @see #suppressGetenvAccess()
*/
public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
/**
* Name of property to set to specify active profiles: {@value}. Value may be comma
* delimited.
* <p>Note that certain shell environments such as Bash disallow the use of the period
* character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
* is in use, this property may be specified as an environment variable as
* {@code SPRING_PROFILES_ACTIVE}.
* @see ConfigurableEnvironment#setActiveProfiles
*/
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
/**
* Name of property to set to specify profiles active by default: {@value}. Value may
* be comma delimited.
* <p>Note that certain shell environments such as Bash disallow the use of the period
* character in variable names. Assuming that Spring's {@link SystemEnvironmentPropertySource}
* is in use, this property may be specified as an environment variable as
* {@code SPRING_PROFILES_DEFAULT}.
* @see ConfigurableEnvironment#setDefaultProfiles
*/
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
/**
* Name of reserved default profile name: {@value}. If no default profile names are
* explicitly and no active profile names are explicitly set, this profile will
* automatically be activated by default.
* @see #getReservedDefaultProfiles
* @see ConfigurableEnvironment#setDefaultProfiles
* @see ConfigurableEnvironment#setActiveProfiles
* @see AbstractEnvironment#DEFAULT_PROFILES_PROPERTY_NAME
* @see AbstractEnvironment#ACTIVE_PROFILES_PROPERTY_NAME
*/
protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
protected final Log logger = LogFactory.getLog(getClass());
private final Set<String> activeProfiles = new LinkedHashSet<>();
private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
/**
* 用来封装系统属性信息及环境变量信息
*/
private final MutablePropertySources propertySources = new MutablePropertySources();
/** 初始化配置文件解析器 */
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
/**
* Create a new {@code Environment} instance, calling back to
* {@link #customizePropertySources(MutablePropertySources)} during construction to
* allow subclasses to contribute or manipulate {@link PropertySource} instances as
* appropriate.
* @see #customizePropertySources(MutablePropertySources)
*/
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
/// ....忽略部门代码
}
我们看到StandardEnvironment没有默认的构造方法,然后我们具体看他父类的构造方法,父类的构造方法调用了customizePropertySources方法也就是StandardEnvironment中的customizePropertySources方法,我们可以看得到这个方法就是为我们去加载一些系统变量跟环境变量。到此我们继续分析resolveRequiredPlaceholders是如何解析配置文件名字的占位符。
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
// this.propertyResolver: PropertySourcesPropertyResolver
// 根据传入的配置文件名称解析配置文件真正的路径,默认调用的是AbstractPropertyResolver抽象实现
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
// 创建PropertyPlaceholderHelper对象
this.strictHelper = createPlaceholderHelper(false);
}
// 执行解析占位符的操作
return doResolvePlaceholders(text, this.strictHelper);
}
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
/**
* Replaces all placeholders of format {@code ${name}} with the value returned
* from the supplied {@link PlaceholderResolver}.
* @param value the value containing the placeholders to be replaced
* @param placeholderResolver the {@code PlaceholderResolver} to use for replacement
* @return the supplied value with placeholders replaced inline
*/
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, null);
}
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
// 如果没有占位符,直接返回.例如:$Value("wangbin33") 这种类型.直接返回wangbin33
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
}
// 解析占位符对应的key: 如${user.name}, ${file-${name}}
StringBuilder result = new StringBuilder(value);
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 == null) {
visitedPlaceholders = new HashSet<>(4);
}
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
/**
* 递归调用parseStringValue方法
* 递归调用的原因是可能会出现类似于:spring-${user.name.${evn}}.xml这样的嵌套占位符.
*/
// Recursive invocation, parsing placeholders contained in the placeholder key. 解析出占位符中的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();
}
我们可以看到在真正解析配置文件的代码中去递归调用解析方法,递归调用的原因是可能会出现类似于:spring-${user.name.${evn}}.xml这样的嵌套占位符.
好了,对于ClassPathXmlApplicationContext的构造方法的前两步我们就先分析到这里,在接下来的篇章中我们就可以分析refresh方法。