前言
前面的文章一直是以BeanFactory接口以及它的默认实现类XMLBeanFactory为例进行分析,但是,在Spring中还提供了另一个接口ApplicationContext,用于扩展BeanFactory中现有的功能。
ApplicationContext和BeanFactory的区别
ApplicationContext和BeanFactory两者都是用于加载Bean的,但是相比之下,ApplicationContext提供了更多的扩展功能,简单一点说就是ApplicationContext包含BeanFactory的所有功能。通常的情况下都是ApplicationContext优先,除非在一些特定的场合,比如字节长度对内存有很大的影响时,Applet。绝大多数的企业应用和系统,ApplicationContext就是你需要使用的。
初识ApplicationContext
先来看下两种不同类的使用方式,也就是怎么去加载配置文件的写法上的不同:
? 使用BeanFactory方式加载XML
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
? 使用ApplicationContext方式加载XML
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
跟BeanFactory的方式一样,还是以ClassPathXmlApplicationContext作为切入点,开始对整体功能进行分析:
public ClassPathXmlApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); }
public ClassPathXmlApplicationContext( String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
上述是ClassPathXmlApplicationContext的两个构造函数,可以看出,设置路径是必不可少的步骤,ClassPathXmlApplicationContext中可以将配置文件路径以数组的方式传入,ClassPathXmlApplicationContext可以对数组进行解析并进行加载。而对于解析及功能实现都在refresh()方法中。
设置配置路径
在ClassPathXmlApplicationContext中支持多个配置文件以数组方式同时传入:
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] = resolvePath(locations[i]).trim(); } } else { this.configLocations = null; } }
上述函数主要用于解析给定的路径数组,当然,如果数组中包含特殊符号,如${var},那么在resolvePath中会搜寻匹配的系统变量并替换。
扩展功能
设置了路径之后,便可以根据路径做配置文件的解析以及各种功能的实现了。可以说refresh函数中包含了几乎ApplicationContext中提供的全部功能,而且此函数中的逻辑非常清晰明了,使我们很容易分析对应的层次及逻辑。
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 准备刷新上下文环境 prepareRefresh(); // 初始化BeanFactory,并进行XML文件读取 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 对BeanFactory进行各种功能填充 prepareBeanFactory(beanFactory); try { // 子类覆盖方法做额外的处理 postProcessBeanFactory(beanFactory); //激活各种BeanFactory处理器 invokeBeanFactoryPostProcessors(beanFactory); //注册拦截bean创建的bean处理器,这里只是注册,真正的调用是在getBean中 registerBeanPostProcessors(beanFactory); //为上下文初始化Message源,即不同语言的消息体,国际化处理 initMessageSource(); // 初始化应用消息广播器,并放入“ApplicationEventMulticaster” bean中 initApplicationEventMulticaster(); //留给子类来初始化其他的bean onRefresh(); // 在所有注册的bean中查找Listener bean,注册到消息广播器中 registerListeners(); // 初始化剩下的单实例(非惰性的) finishBeanFactoryInitialization(beanFactory); //完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人 finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // 销毁已经创建的单例,以避免空资源。 destroyBeans(); // 重置“活着”标志 cancelRefresh(ex); throw ex; } finally { // 重置Spring内核中的常用自检缓存,因为我们可能再也不需要单例bean的元数据了…… resetCommonCaches(); } } }
上述代码中包括了很多的步骤,下面从中解释一下其中提供的功能。
(1)初始化前的准备工作,例如对系统属性或者环境变量进行准备及验证;
在某种情况下项目的使用需要读取某些系统变量,而这个变量的设置很可能会影响着系统的正确性,那么ClassPathXmlApplicationContext为我们提供的这个准备函数就显得非常必要,它可以在Spring启动的时候提前对必须的变量进行存在性验证。
(2)初始化BeanFactory,并进行XML文件读取;
之前提到过ClassPathXmlApplicationContext 包含着BeanFactory所提供的一切特征,那么在这一步中将会复用BeanFactory中的配置文件的读取解析以及其他功能,这一步之后,ClassPathXmlApplicationContext实际上就已经包含了BeanFactory所提供的功能,也就是可以进行Bean的读取等基础的操作了。
(3)对BeanFactory进行各种功能填充;
@Qualifier与@Autowire应该是大家非常熟悉的注解,那么这两个注解正是在这一步骤中增加的支持。
(4)子类覆盖方法做额外的处理;
Spring之所以强大,除了它功能上为大家提供了便利外,还有一方面是它的完美架构,开放式的架构让使用它的程序员很容易根据业务需求来扩展已经存在的功能。这种开放式的设计在Spring中随处可见,例如在本例中就提供了一个空的函数实现postProcessBeanFactory来方便程序员在业务上做进一步的扩展。
(5)激活各种BeanFactory处理器;
(6)注册拦截bean创建的bean处理器,这里只是注册,真正的调用是在getBean的时候。
(7)为上下文初始化Message源,即对不同语言的消息体进行国际化处理;
(8)初始化应用消息广播器,并放入“applicantionEventMulticaster”bean中;
(9)留给子类来初始化其他的bean;
(10)在所有注册的bean中查找listener bean,注册到消息广播器中;
(11)初始化剩下的单实例(非惰性的);
(12)完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人。
下面接下来的文章会对这些步骤进行详细的解析,这篇文章只是对ApplicationContext的解析步骤进行大致上的一个整体的概括。
参考:《Spring源码深度解析》 郝佳 编著: