IOC容器初始化过程
上一篇文章我们讲解了spring容器中IOC的容器结构,IOC容器总共有:1.ResourceLoader(资源加载组件)2.Resource(资源描述组件)3.BeanDefinitionReader(bean构建组件)4.BeanDefinition(元数据组件)5.BeanRegister(Bean注册组件)6.BeanFactory(bean容器组件)
他们在spring的源码的初始化过程是怎么的呢?在这一节,我们通过查看、分析Spring源码进一步的去理解SpringIOC容器的启动流程是怎么样的。
在上一节,我重点讲解了SpringIOC容器中的Bean容器组件,其中Bean容器组件中有两个常用的容器,一个是BeanFactory,还有一个是ApplicationContext。而且在上节我也说过,其他组件都是服务于Bean容器的,因此,我们要探究IOC容器的初始化过程需要我们将重点放在Bean容器上,那么,接下来我们就拿在开发中最常用的Bean容器体系中的ClassPathXmlApplicationContext去讲解SpringIOC的初始化过程。
在还没有springboot之前,我们使用spring大部分会通过xml去定义bean然后让ClassPathXmlApplicationContext去读取、解析资源文件,进而让spring去管理xml中定义的bean。在这个过程中,我们都会在程序入口中new一个ClassPathXmlApplicationContext对象,并把表示资源文件的路径通过构造器进行获取(这里spring几种资源加载方式在上篇文章有讲解,如果不记得的小伙伴可以再回过头去看一下),其实SpringIOC的初始化也是在这个Bean容器类初始化的过程中实现的。
那我们就去这个对象的构造方法中看一下。这个类中有很多的重载的构造方法,但是这些重方法最终都会调用下面的这个构造方法。
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if (refresh) {
this.refresh();
}
}
从上面的代码中我们可以看出会先调用父类的构造器方法,这里有一个构造器的构造器的调用过程:ClassPathXmlApplicationContext---->AbstractXmlApplicationContext----->AbstractRefreshableConfigApplicationContext---->AbstractRefreshableApplicationContext----->AbstractApplicationContext(注意这里的调用过程并不是说的jvm中的栈方法执行的过程,jvm中栈的执行过程恰恰与这个调用过程相反,会最先执行AbstractApplicationContext的构造器方法)。在类的实例化过程中其实只有AbstractApplicationContext的构造器方法中有真正实例化容器的实现逻辑。
public AbstractApplicationContext() {
this.logger = LogFactory.getLog(this.getClass());
this.id = ObjectUtils.identityToString(this);
this.displayName = ObjectUtils.identityToString(this);
this.beanFactoryPostProcessors = new ArrayList();
this.active = new AtomicBoolean();
this.closed = new AtomicBoolean();
this.startupShutdownMonitor = new Object();
this.applicationListeners = new LinkedHashSet();
this.resourcePatternResolver = this.getResourcePatternResolver();
}
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
this.setParent(parent);
}
在这个方法中,它对容器的一些基本参数进行了一个初始化,需要注意的是,这里的parent如果没有在ClassPathXmlApplicationContext构造器中指定的话,默认是为null的。在这个基本参数的一个设置中,需要我们关注一下resourcePatternResolver这个属性值的获取过程,如果大家认真看了我上一篇文章,应该对这个名字会有点熟悉。他就是ResourceLoader体系中有着解析资源功能的组件。这里我们看一下它的获取过程。
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}
它通过调用上面方法去获取一个PathMatchingResourcePatternResolver对象的实例,而在创建PathMatchingResourcePatternResolver实例的过程中指定AbstractApplicationContext为PathMatchingResourcePatternResolver的resourceLoader为了便于之后去读取资源文件。
好了,super(parent); 调用逻辑已经分析完毕,我们再来看看this.setConfigLocations(configLocations); 做了些什么。
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] = this.resolvePath(locations[i]).trim();
}
} else {
this.configLocations = null;
}
}
在这个方法中,先是将资源的描述字符串放入到String[] 中,然后就是通过this.resolvePath(locations[i]).trim(); ,这个方法的主要功能就是对传入的如applicationContext-${module}.xml中的占位符进行解析。
protected String resolvePath(String path) {
return this.getEnvironment().resolveRequiredPlaceholders(path);
}
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = this.createEnvironment();
}
return this.environment;
}
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}
由看上的我放上去的几个方法,this.getEnvironment()这个获取的是StandardEnvironment的对象,然后通过这个对象调用resolveRequiredPlaceholders方法,并把path传进去。
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
return this.propertyResolver.resolveRequiredPlaceholders(text);
}
上面是StandardEnvironment中resolveRequiredPlaceholders方法的具体逻辑。在这个方法里面,他通过propertyResolver属性中的resolveRequiredPlaceholders方法去解析path,而这个propertyResolver属性,是StandardEnvironment的父类AbstractEnvironment随StandardEnvironment的new过程指定的。
public AbstractEnvironment() {
this.propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);
this.customizePropertySources(this.propertySources);
}
PropertySourcesPropertyResolver这个类中并没有实现resolveRequiredPlaceholders方法而是在PropertySourcesPropertyResolver的父类AbstractPropertyResolver中有实现。
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
this.strictHelper = this.createPlaceholderHelper(false);
}
return this.doResolvePlaceholders(text, this.strictHelper);
}
这里他会创建一个PropertyPlaceholderHelper的类,然后调用this.doResolvePlaceholders(text, this.strictHelper);方法。典型的面向对象编程,总喜欢用一个helper类去完成一些功能。真正执行逻辑的也肯定是这个helper类,大家一定要有这种概念存在。
这个方法调用到最后是通过helper类中的parseStringValue()方法去实现的。
protected String parseStringValue(String value, PropertyPlaceholderHelper.PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
} else {
StringBuilder result = new StringBuilder(value);
while(startIndex != -1) {
int endIndex = this.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 (!((Set)visitedPlaceholders).add(placeholder)) {
throw new IllegalArgumentException("Circular placeholder reference '" + placeholder + "' in property definitions");
}
placeholder = this.parseStringValue(placeholder, placeholderResolver, (Set)visitedPlaceholders);
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) {
propVal = this.parseStringValue(propVal, placeholderResolver, (Set)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) {
throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "' in value \"" + value + "\"");
}
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
((Set)visitedPlaceholders).remove(originalPlaceholder);
} else {
startIndex = -1;
}
}
return result.toString();
}
}
这个方法的功能就是对path中的占位符的一个解析。
讲解这个方法需要一篇博客才能真正的讲解透彻,但是这篇文章的重点在于IOC容器的初始化过程,就不用过多的篇幅去讲解了,如果感兴趣的小伙伴可以自己去看一下。
好了,到此,我们已经讲解完ClassPathXmlApplicationContext构造器中的 super(parent)方法和this.setConfigLocations(configLocations)方法,接下来就是refresh方法。
if (refresh) {
this.refresh();
}
这个方法在ClassPathXmlApplicationContext中没有实现,而是在AbstractApplicationContext类中,下面贴上这个方法的代码。
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
在这篇文章中,我们只需要知道这个方法中完成了springIOC容器大部分的功能,具体reflesh中每一个方法调用实现那些功能呢,在接下来Spring系列会做一个详细的讲解。