写点什么

Spring 系列之 IOC 容器的实例化过程一

作者:王威07325
  • 2023-05-29
    上海
  • 本文字数:5761 字

    阅读完需:约 19 分钟

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 系列会做一个详细的讲解。

发布于: 刚刚阅读数: 3
用户头像

王威07325

关注

还未添加个人签名 2021-12-29 加入

还未添加个人简介

评论

发布
暂无评论
Spring系列之IOC容器的实例化过程一_spring ioc_王威07325_InfoQ写作社区