上一节分析了父容器(ClasspathXmlApplicationContext)的加载,其加载的配置文件可以在web.xml中配置,但是并没有仔细的去研究父容器的创建的过程,那是因为父容器的创建与子容器的创建基本一致。本节将分析ApplicationContext与BeanFactory的刷新过程。
2.1 配置文件的解析
context创建后,接下来就需要准备好配置文件,以供后面解析配置做准备。
//(0) 配置和刷新context
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
//底层使用了JDK提供的类名+@+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的类图
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的能力)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?