容器的功能扩展和refresh方法解析
在之前文章中我们了解了关于Spring中bean的加载流程,并一直使用BeanFactory接口以及它的默认实现类XmlBeanFactory,在Spring中还提供了另一个接口ApplicationContext,用于扩展BeanFactory中现有的功能。
首先BeanFactory和ApplicationContext都是用于加载bean的,但是相比之下,ApplicationContext提供了更多的扩展功能,ApplicationContext包含了BeanFactory的所有功能。通常我们会优先使用ApplicationContext。
我们来看看ApplicationContext多了哪些功能?
首先看一下写法上的不同。
使用BeanFactory方式加载XML
final BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
使用ApplicationContext方式加载XML
final ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
我们开始点开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中可以将配置文件路径以数组的形式传入,对解析及功能实现都在refresh()
方法中实现。
设置配置路径
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) { // 准备刷新的上下文环境,包括设置启动时间,是否激活标识位 // 初始化属性源(property source)配置 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(); // 初始化剩下的单例bean (非惰性) finishBeanFactoryInitialization(beanFactory); //完成刷新过程,通知生命周期处理器LifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知别人 finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源 destroyBeans(); // 重置活动标志 cancelRefresh(ex); throw ex; } finally { //重置公共缓存 resetCommonCaches(); } } }
我们总结一下初始化的步骤。
- 初始化前的准备工作,例如对系统属性或者环境变量进行准备及验证
- 初始化BeanFactory,并对XML文件进行读取。之前我们说过ClassPathXmlApplicationContext中包含着BeanFactory所提供的一切特征,那么在这一步将会复用BeanFactory中的配置文件读取解析及其他功能,在这一步之后ClassPathXmlApplicationContext就已经包含了BeanFactory所提供的功能,也就是可以对bean进行提取等操作
- 对BeanFactory进行各种功能填充
- 子类覆盖方法做额外的处理。主要用于我们在业务上做进一步扩展
- 激活各种BeanFactory处理器
- 注册拦截bean创建的bena处理器,这里仅仅是注册,真正调用在getBean中
- 为上下文初始化Message源,对不同语言的消息体进行国际化处理
- 初始化应用消息广播器,并放入"applicationEventMulticaster" bean中
- 留给子类来初始化其他的bean
- 在所有注册的bean中查找listener bean,注册到消息广播器中
- 初始化剩下的单实例(非惰性)
- 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent来通知别人
环境准备
prepareRefresh方法主要做些准备工作,比如对系统属性及环境变量的初始化及验证。
initPropertySources
该方法里面是一个空实现,主要用于给我们根据需要去重写该方法,并在方法中进行个性化的属性处理及设置。
protected void initPropertySources() { // For subclasses: do nothing by default. }
validateRequiredProperties
该方法主要对属性进行验证。默认情况下什么也没校验。在我们继承了ClassPathXmlApplicationContext类重写了initPropertySources方法后会进行相关校验。
加载BeanFactory
obtainFreshBeanFactory 方法主要用来获取BeanFactory,刚才说过ApplicationContext拥有BeanFactory的所有功能,这个方法就是实现BeanFactory的地方,也就是说调用完该方法后,applicationContext就拥有了BeanFactory的功能。
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { //初始化BeanFactory,并进行XML文件读取,将得到的BeanFactory记录到当前实体属性中 refreshBeanFactory(); //返回当前实体的beanFactory属性 return getBeanFactory(); }
protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
我们进入AbstractRefreshableApplicationContext#refreshBeanFactory()
方法中。
protected final void refreshBeanFactory() throws BeansException { //判断是否存在beanFactory if (hasBeanFactory()) { //销毁所有单例 destroyBeans(); //重置beanFactory closeBeanFactory(); } try { //创建beanFactory DefaultListableBeanFactory beanFactory = createBeanFactory(); //设置序列化id beanFactory.setSerializationId(getId()); //定制beanFactory,设置相关属性,包括是否允许覆盖同名称不同定义的对象以及循环依赖 customizeBeanFactory(beanFactory); //初始化DocumentReader,进行XML读取和解析 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
总结一下这个方法的流程:
- 创建DefaultListableBeanFactory。声明方式为:
BeanFactory bf = new XmlBeanFactory("beanFactoryTest.xml")
,其中的XmlBeanFactory继承自DefaultListableBeanFactory,并提供了XmlBeanDefinitionReader类型的reader属性,也就是说DefaultListableBeanFactory是容器的基础,必须首先实例化,这里就是实例化DefaultListableBeanFactory的步骤 - 指定序列化ID
- 定制BeanFactory
- 加载BeanDefinition
- 使用全局变量记录BeanFactory类实例
定制BeanFactory
首先我们先了解customizeBeanFactory方法,该方法是在基本容器的基础上,增加了是否允许覆盖、是否允许扩展的设置。
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { //如果不为空,设置beanFactory对象响应的属性,含义:是否允许覆盖同名称的不同定义的对象 if (this.allowBeanDefinitionOverriding != null) { beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } //如果属性不为空,设置给beanFactory对象相应属性,含义:是否允许bean之间存在循环依赖 if (this.allowCircularReferences != null) { beanFactory.setAllowCircularReferences(this.allowCircularReferences); } }
具体这里只是做了简单的判断,具体设置属性的地方,使用子类覆盖即可。例如:
/** * @author 神秘杰克 * 公众号: Java菜鸟程序员 * @date 2022/6/12 * @Description 自定义ClassPathXmlApplicationContext */ public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext { @Override protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { super.setAllowBeanDefinitionOverriding(false); super.setAllowCircularReferences(false); super.customizeBeanFactory(beanFactory); } }
加载BeanDefinition
在初始化了DefaultListableBeanFactory后,我们还需要XmlBeanDefinitionReader来读取XML文件,这个步骤中首先要做的就是初始化XmlBeanDefinitionReader。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { //为指定beanFactory创建XmlBeanDefinitionReader XmlBeanDefinitionReader beanDefinitionReader = n