版本 Spring Framework 6.0.9
在学习SpringIOC的时候,ClassPathXmlApplicationContext经常作为入门的IOC容器,浅析下IOC容器的创建过程。
1. 构造方法
ClassPathXmlApplicationContext的构造方法可以分成两种,一种是参数有class对象的构造方法,另外一种则没有,主要区别在于资源路径的处理。其他构造方法最终调用其中一种,
1.1 不带class对象参数的构造方法
调用父类AbstractRefreshableConfigApplicationContext的setConfigLocations方法,将资源路径解析后,添加到父类属性configLocations数组中,数组类型是String
1.2 带class对象参数的构造方法
将资源路径转换成ClassPathResource对象,赋值给上下文的属性configResources数组中,数组类型是Resource
1.3 实例化ClassPathXmlApplicationContext
实际上不带class对象参数的构造方法,path在Spring的refresh方法中obtainFreshBeanFactory阶段,最终还是会转换成Resource对象,调用org.springframework.beans.factory.xml.XmlBeanDefinitionReader的 public int loadBeanDefinitions(EncodedResource encodedResource)方法。
参考官方示例,使用不带class对象参数的构造方法创建IOC容器
2. 实例化过程
构造方法执行过程分为三个部分,调用父类的构造方法、设置上下文的配置路径,调用refresh()方法
2.1 super(parent)
2.1.1 源码
super(parent)调用父类AbstractApplicationContext的有参构造,包含两段代码
// this()
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
// getResourcePatternResolver()
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
// new PathMatchingResourcePatternResolver(this)
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
this()实际上是AbstractApplicationContext的无参构造方法,该方法创建了一个资源路径解析器PathMatchingResourcePatternResolver并赋值给上下文的resourcePatternResolver属性
另外PathMatchingResourcePatternResolver是包含当前上下文对象的,给该实例的resourceLoader属性赋值,提供加载资源的能力。
PathMatchingResourcePatternResolver用于在上下文refresh过程中,加载bean定义loadBeanDefinitions时将配置路径转成Resource对象。
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
// 父环境非空并且是ConfigurableEnvironment实例,将父环境合并到当前(子)环境中
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment configurableEnvironment) {
// 如果当前上下文没有环境对象,getEnvironment会先创建一个,再合并
getEnvironment().merge(configurableEnvironment);
}
}
}
// getEnvironment().merge(configurableEnvironment);
public void merge(ConfigurableEnvironment parent) {
// 合并环境配置
for (PropertySource<?> ps : parent.getPropertySources()) {
if (!this.propertySources.contains(ps.getName())) {
this.propertySources.addLast(ps);
}
}
// 合并激活环境配置
String[] parentActiveProfiles = parent.getActiveProfiles();
if (!ObjectUtils.isEmpty(parentActiveProfiles)) {
synchronized (this.activeProfiles) {
Collections.addAll(this.activeProfiles, parentActiveProfiles);
}
}
// 合并默认环境配置
String[] parentDefaultProfiles = parent.getDefaultProfiles();
if (!ObjectUtils.isEmpty(parentDefaultProfiles)) {
synchronized (this.defaultProfiles) {
this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);
Collections.addAll(this.defaultProfiles, parentDefaultProfiles);
}
}
}
setParent(parent);主要作用是设置上下文的parent属性。如果父环境非空,则将父环境属性添加到当前环境,本文示例没传父上下文。
2.1.2 总结
- 实例化PathMatchingResourcePatternResolver,给属性resourcePatternResolver赋值
- 给属性parent赋值,当父环境不为空是,合并父子环境
2.2 setConfigLocations(configLocations)
2.2.1 源码
上面提过该方法是用来解析上下文配置路径,这里循环locations数组解析资源路径,入参为 services.xml 和 beans.xml。
resolvePath方法也是两段代码,getEnvironment和resolveRequiredPlaceholders
-
getEnvironment
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
// 创建一个StandardEnvironment
this.environment = createEnvironment();
}
return this.environment;
}
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}
// org.springframework.core.env.StandardEnvironment#StandardEnvironment()
public StandardEnvironment() {
}
getEnvironment()方法主要是获取当前上下文环境,但也有创建环境的能力,赋值给上下文的environment属性。本文示例在这里创建ClassPathXmlApplicationContext的环境StandardEnvironment。
public class StandardEnvironment extends AbstractEnvironment {
public StandardEnvironment() {
}
// 省略其他...
}
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
public AbstractEnvironment() {
this(new MutablePropertySources());
}
protected AbstractEnvironment(MutablePropertySources propertySources) {
this.propertySources = propertySources;
// 创建一个属性源解析器PropertySourcesPropertyResolver
this.propertyResolver = createPropertyResolver(propertySources);
// 子类自定义操作propertySources
customizePropertySources(propertySources);
}
protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {
return new PropertySourcesPropertyResolver(propertySources);
}
protected void customizePropertySources(MutablePropertySources propertySources) {
}
// 省略其他代码...
}
虽然StandardEnvironment的无参构造是个空方法,但其父类AbstractEnvironment的无参构造对环境做了一些初始化。
无参构造创建了一个MutablePropertySources对象(包含一个或多个
PropertySource源对象,实现并拓展了接口PropertySources操作属性源能力),作为入参调用另外一个有参构造。
MutablePropertySources对象赋值给了环境StandardEnvironment的propertySources属性
createPropertyResolver方法创建一个PropertySourcesPropertyResolver,赋值给环境StandardEnvironment的propertyResolver属性,提供解析属性源的能力。
customizePropertySources是个空方法,由子类实现。在springframework体系中,两个子类实现了该方法,一个是web环境的StandardServletEnvironment,另外一个是非web环境,即本文示例中创建的StandardEnvironment。
public class StandardEnvironment extends AbstractEnvironment {
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
// JVM 系统属性属性源,名称systemProperties
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
// 系统属性属性源,名称systemEnvironment
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
// 省略其他代码...
}
StandardEnvironmen环境下,重写的customizePropertySources方法会加载JVM 系统属性和系统属性。
public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
// Servlet 配置初始化参数属性源,名称servletConfigInitParams
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
// Servlet 上下文初始化参数属性源,名称servletContextInitParams
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (jndiPresent && JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
// JNDI 属性源,名称jndiProperties
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
// 调用org.springframework.core.env.StandardEnvironment.customizePropertySources
// JVM 系统属性属性源,名称systemProperties
// 系统属性属性源,名称systemEnvironment
super.customizePropertySources(propertySources);
}
//省略其他代码...
}
StandardServletEnvironment环境下,除了加载JVM 系统属性和系统属性,还会加载web环境的属性源(Servlet 配置初始化参数属性源、Servlet 上下文初始化参数属性源,JNDI 属性源)
另外当前Servlet 配置属性源StubPropertySource和Servlet 上下文属性源StubPropertySource只是一个占位符,如果上下文的属性对象ServletConfig和ServletContext不为空,则会在refresh#prepareRefresh阶段替换成真正的属性源。
-
resolveRequiredPlaceholders
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
private final ConfigurablePropertyResolver propertyResolver;
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
// 省略其他代码...
}
由上面可以知道,本文示例创建的环境类是StandardEnvironmen,该类继承了抽象类AbstractEnvironment。第二段代码即调用父类AbstractEnvironment的resolveRequiredPlaceholders方法。同时我们也可以知道AbstractEnvironment的propertyResolver属性值是在getEnvironment()方法中,初始化环境创建的PropertySourcesPropertyResolver。
第二段代码最终调用的是 PropertySourcesPropertyResolver 的 父类AbstractPropertyResolver#resolveRequiredPlaceholders方法
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
//省略其他代码...
}
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
@Nullable
private PropertyPlaceholderHelper strictHelper;
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
this.strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, this.strictHelper);
}
//省略其他代码...
}
AbstractPropertyResolver#resolveRequiredPlaceholders方法包含两部分,给strictHelper属性赋值,调用doResolvePlaceholders方法。
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX;
@Nullable
private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR;
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
// 返回一个使用 ${ 作为前缀、 } 作为后缀、 : 作为占位符变量与关联的默认值之间的分隔符的PropertyPlaceholderHelper实例
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
this.valueSeparator, ignoreUnresolvablePlaceholders);
}
//省略其他代码...
}
public abstract class SystemPropertyUtils {
public static final String PLACEHOLDER_PREFIX = "${";
public static final String PLACEHOLDER_SUFFIX = "}";
public static final String VALUE_SEPARATOR = ":";
//省略其他代码...
}
createPlaceholderHelper方法创建一个使用 ${ 作为前缀、 } 作为后缀、 : 作为占位符变量与关联的默认值之间的分隔符的PropertyPlaceholderHelper实例,参数ignoreUnresolvablePlaceholders用于是否忽略无法解析的占位符,这里值为false。即AbstractPropertyResolver的strictHelper属性是一个PropertyPlaceholderHelper实例。
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver {
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
@Nullable
protected abstract String getPropertyAsRawString(String key);
// 省略其他代码...
}
doResolvePlaceholders方法的内容是调用刚刚实例化的PropertyPlaceholderHelper对象的replacePlaceholders(String value, PlaceholderResolver placeholderResolver)方法
public class PropertyPlaceholderHelper {
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是否包含前缀placeholderPrefix
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
}
StringBuilder result = new StringBuilder(value);
while (startIndex != -1) {
// 截取${}中的值
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
// 省略其他代码...
// 获取环境中名称为placeholder的属性
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
// 省略其他代码...
}
return result.toString();
}
@FunctionalInterface
public interface PlaceholderResolver {
/**
* Resolve the supplied placeholder name to the replacement value.
* @param placeholderName the name of the placeholder to resolve
* @return the replacement value, or {@code null} if no replacement is to be made
*/
@Nullable
String resolvePlaceholder(String placeholderName);
}
// 省略其他代码...
}
replacePlaceholders方法有两个入参,参数value是资源路径,值为本文示例的services.xml或beans.xml,参数placeholderResolver是函数式接口,值为抽象类AbstractPropertyResolver 的抽象方法getPropertyAsRawString,回想一下,现在我们一直看的都是抽象类AbstractPropertyResolver 的源码,但当前实例对象其实是其子类PropertySourcesPropertyResolver,所以parseStringValue方法里调用函数式接口的方法placeholderResolver.resolvePlaceholder(placeholder),实际上是抽象类AbstractPropertyResolver子类方法PropertySourcesPropertyResolver#getPropertyAsRawString。
函数式接口的唯一方法虽然不被当成抽象方法,实际上是抽象方法
本文示例在parseStringValue方法,value的值为services.xml或beans.xml,不包含 "${" 匹配符,都直接退出了。
拓展一下,将main方法中创建ClassPathXmlApplicationContext对象的代码改成如下
往系统添加属性servicesPath=services.xml,将资源路径services.xml改为占位符的方式${servicesPath},重新运行main方法
这样匹配逻辑就通过了
继续debug代码,可以发现${servicesPath}最终可以解析成系统servicesPath属性的关联值services.xml。
2.2.2 总结
- 获取环境,当环境为空时创建环境并初始化,给上下文属性environment赋值
- 解析资源路径,如果存在占位符,则获取占位符的关联值,给上下文属性configLocations赋值
2.3 refresh()
2.3.1 源码
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 标志context的刷新方法开始
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// 准备上下文环境,设置启动时间、活动状态,初始化属性源和事件监听器等。
prepareRefresh();
// 重置BeanFactory,加载Bean定义并返回 Bean 工厂, 获取的是DefaultListableBeanFactory,。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 对beanFactory填充功能,进行一些配置,如设置类加载器、表达式解析器、属性编辑器等。
prepareBeanFactory(beanFactory);
try {
// 留给子类覆盖,对BeanFactory进行额外的处理。
postProcessBeanFactory(beanFactory);
// 标志后置处理器相关处理步骤开始
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// 激活BeanFactoryPostProcessor,允许BeanFactoryPostProcessor对BeanFactory进行后置处理
invokeBeanFactoryPostProcessors(beanFactory);
// 注册BeanPostProcessor,用于Bean实例化时的回调处理。
registerBeanPostProcessors(beanFactory);
// 标识后置处理器相关处理步骤已完成
beanPostProcess.end();
// 初始化国际化相关的属性
initMessageSource();
// 初始化事件广播器,用于事件的发布和监听。
initApplicationEventMulticaster();
// 模板方法,留给子类覆盖,用于扩展refresh动作。
onRefresh();
// 注册事件监听器,从BeanFactory中找出实现了ApplicationListener接口的Bean并注册到事件广播器中。
registerListeners();
// 实例化所有非懒加载的单例Bean,调用BeanFactory的preInstantiateSingletons()方法。
finishBeanFactoryInitialization(beanFactory);
// 完成刷新过程,通知生命周期处理器和事件监听器,发布ContextRefreshedEvent事件。
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 销毁已创建的单例bean
destroyBeans();
// 设置“active”属性为false
cancelRefresh(ex);
throw ex;
}
finally {
// 用于重置Spring的一些公共的反射元数据缓存,包括ReflectionUtils,AnnotationUtils,ResolvableType和CachedIntrospectionResults等
// 这些缓存是为了提高Spring的性能和效率,但是在某些情况下,可能需要清除这些缓存,比如在刷新上下文或者切换类加载器的时候.
resetCommonCaches();
// 标志context的刷新方法结束
contextRefresh.end();
}
}
}
调用父类AbstractApplicationContext的refresh方法,这里贴一下大致流程,另写个文章浅析一下。
2.3.2 总结
- 无