基于 Spring Framework v5.2.6.RELEASE
在使用 XML 文件初始化 Spring 容器时,通常会用到以下的方式:
ApplicationContextcontext=newClassPathXmlApplicationContext("beans.xml"); Useruser=context.getBean("user", User.class); user.sayHello();
其中,beans.xml
文件中定义了一些 Bean,在 ClassPathXmlApplicationContext
的构造方法中,通过读取 beans.xml 中的配置,创建了 Spring 容器。ClassPathXmlApplicationContext
是 ApplicationContext
的实现类,ApplicationContext
继承自 BeanFactory
。我们可以通过它的 getBean
方法,从 Spring 容器中获取 Bean 对象。
ClassPathXmlApplicationContext
的构造方法
我们从 ClassPathXmlApplicationContext
的构造方法开始,在源码中探索 XML 配置文件是如何加载进来的。
上面的代码中,我们使用了以下的构造方法:
publicClassPathXmlApplicationContext(StringconfigLocation) throwsBeansException { this(newString[] {configLocation}, true, null); }
在这里,调用了另外一个构造方法,定义如下:
publicClassPathXmlApplicationContext( String[] configLocations, booleanrefresh, ApplicationContextparent) throwsBeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
综合看以上的两个构造方法,这里使用了三个参数:
configLocations
是我们创建ClassPathXmlApplicationContext
时提供的配置文件路径,从方法签名中看出,这里可以提供多个配置文件。refresh
表示是否自动刷新上下文。这里刷新上下文还包括重新加载所有的 BeanDefinition、创建单例 Bean 等。这里的代码中调用方法时的值时true
。parent
是当前容器的父容器,这里代码中直接提供了null
。
接下来我们看方法体中的内容,主要有三句代码,我们一一来探索。
ClassPathXmlApplicationContext
构造方法中的内容
调用父类构造方法
第一步,以 parent
为参数调用了父类的构造方法。下图是 ClassPathXmlApplicationContext
类的继承关系图:
顺着父类构造方法的调用一路跟踪,我们可以找到以下代码:
publicAbstractApplicationContext(ApplicationContextparent) { this(); setParent(parent); }
这里最终调用到了 AbstractApplicationContext
的构造方法,其中有两行逻辑。
- 调用了无参构造方法
- 调用了
setParent(parent)
。
我们分别来看。
无参构造方法的代码如下:
publicAbstractApplicationContext() { this.resourcePatternResolver=getResourcePatternResolver(); }
protectedResourcePatternResolvergetResourcePatternResolver() { returnnewPathMatchingResourcePatternResolver(this); }
这里初始化了 resourcePatternResolver
成员变量,PathMatchingResourcePatternResolver
是一个基于路径匹配的资源解析器。
之后的 setParent
方法源码如下:
publicvoidsetParent(ApplicationContextparent) { this.parent=parent; if (parent!=null) { EnvironmentparentEnvironment=parent.getEnvironment(); if (parentEnvironmentinstanceofConfigurableEnvironment) { getEnvironment().merge((ConfigurableEnvironment) parentEnvironment); } } }
这里将传进来的 parent
参数复制给了对应的成员变量。因为前面的代码中,实际传入的值为 null
,因此之后的 if 语句块中的内容我们先不看。
setConfigLocations
处理配置文件路径中的占位符
在 ClassPathXmlApplicationContext
构造方法的第二步,调用了 setConfigLocations
方法:
publicvoidsetConfigLocations(String... locations) { if (locations!=null) { Assert.noNullElements(locations, "Config locations must not be null"); this.configLocations=newString[locations.length]; for (inti=0; i<locations.length; i++) { this.configLocations[i] =resolvePath(locations[i]).trim(); } } else { this.configLocations=null; } }
这里的主要步骤,是把我们实例化 ClassPathXmlApplicationContext
时传递的配置文件路径,放到了一个新创建的字符串数组中,并将其赋值给 configLocations
成员变量。
在放入新输入之前,还调用了 resolvePath
方法对文件路径字符串进行了处理,我们看一下这个方法的源码:
protectedStringresolvePath(Stringpath) { returngetEnvironment().resolveRequiredPlaceholders(path); }
再通过 resolveRequiredPlaceholders
方法的接口定义,我们可以了解到,这里主要是为了处理文本中的 ${}
占位符。也就是说,这里给定的路径中可以包含 ${}
占位符,并且在创建 Spring 容器时,Spring 可以处理这些占位符,前提是,在当前的 Spring 环境中,能通过这些占位符找到匹配的值。
refresh 核心方法
在创建容器的构造方法中,refresh
是最核心的方法,这个方法在 AbstractApplicationContext
中定义。我们先看看 refresh
方法的源码:
publicvoidrefresh() throwsBeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing.// 初始化上下文的信息prepareRefresh(); // Tell the subclass to refresh the internal bean factory.// 这里会调用模版方法,通过子类的实现,初始化 BeanFactory 并解析 XML 配置ConfigurableListableBeanFactorybeanFactory=obtainFreshBeanFactory(); // Prepare the bean factory for use in this context.// 对 BeanFactory 进行初始配置prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses.// 这里默认是一个空的模版方法,子类可以在此处实现逻辑postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context.// 执行 BeanFactory 的后处理器invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation.// 注册 Bean 的后处理器registerBeanPostProcessors(beanFactory); // Initialize message source for this context.// 初始化消息源initMessageSource(); // Initialize event multicaster for this context.// 初始化时间广播器initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses.// 这里也是一个空的模版方法,子类可以在此处实现逻辑,在容器中的单例 Bean 初始化之前,来初始化一些特殊的 BeanonRefresh(); // Check for listener beans and register them.// 注册化监听器registerListeners(); // Instantiate all remaining (non-lazy-init) singletons.// 完成 BeanFactory 初始化,实例化所有非延迟加载的单例 BeanfinishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event.// 初始化结束,广播相应的事件finishRefresh(); } catch (BeansExceptionex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - "+"cancelling refresh attempt: "+ex); } // Destroy already created singletons to avoid dangling resources.destroyBeans(); // Reset 'active' flag.cancelRefresh(ex); // Propagate exception to caller.throwex; } finally { // Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches(); } } }
为了方便理解,我在代码里添加了中文注释,如果你对 Spring 容器初始化的流程有大概的了解,就可以发现,在这个方法中,包含了 Spring 容器初始化的所有核心步骤。
本文先介绍这么多,后面会对 refresh()
方法的详细流程进行探索。