Spring5源码分析(二) IOC 容器的初始化(一)

一,概述

  IOC 容器的初始化包括 BeanDefinition 的 Resource 定位、载入和注册这三个基本的过程。我们以 ApplicationContext 为例讲解来深入讲解,ApplicationContext系列容器也许是我们最熟悉的,因为 Web项目中使用的XmlWebApplicationContext就属于这个继承体系,还有
ClasspathXmlApplicationContext 等,其继承体系如下图所示:
这里写图片描述

ApplicationContext允许上下文嵌套,通过保持父上下文可以维持一个上下文体系。对于 Bean 的查找可以在这个上下文体系中发生,首先检查当前上下文,其次是父上下文,逐级向上,这样为不同的Spring 应用提供了一个共享的 Bean 定义环境。

  下面我们分别简单地演示一下两种 IOC 容器的创建过程,一种是XmlBeanFactory 一种是FileSystemXmlApplicationContext 其中XmlBeanFactory已经被标记为过时的仅仅做简单讲述因为在Spring5.0中XmlBeanFactory已经被标记为过时的,为什么还要说呢!因为XmlBeanFactory相对纯粹也相对简单。

二,XmlBeanFactory IOC容器创建的流程

通过 XmlBeanFactory 的源码,我们可以发现:

public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
    public XmlBeanFactory(Resource resource) throws BeansException {
    this(resource, null);
    }
    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    super(parentBeanFactory);
    this.reader.loadBeanDefinitions(resource);
    }
}

定位是根据Resource来完成,载入是根据BeanDefinitionReader,剩下就是注册,注册是根据ListableBeanFactory。我们可以根据下面的代码跟进 去,去理解定位、载入、注册的全过程。

// 根据 Xml 配置文件创建 Resource 资源对象,该对象中包含了 BeanDefinition 的信息
ClassPathResource resource = new ClassPathResource("application-context.xml");
// 创建 DefaultListableBeanFactory
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
//创建 XmlBeanDefinitionReader 读取器,用于载入 BeanDefinition。
// 之所以需要 BeanFactory 作为参数,是因为会将读取的信息回调配置给 factory
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
// XmlBeanDefinitionReader 执行载入 BeanDefinition 的方法,最后会完成 Bean 的载入和注册。
// 完成后 Bean 就成功的放置到 IOC 容器当中,以后我们就可以从中取得 Bean 来使用
reader.loadBeanDefinitions(resource);

通过前面的源码,XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);中其中 this 传的是 factory 对象

下面我给出自己跟进去的时序图里面标红的为主要的方法需要仔细看:
这里写图片描述
具体类这里不做详细的讲解,下面FileSystemXmlApplicationContext 容器创建会对重要方法做详细说明。

二,FileSystemXmlApplicationContext IOC容器创建的流程

ApplicationContext = new FileSystemXmlApplicationContext(xmlPath);

先看其构造函数的调用:

public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
}

其实际调用的构造函数为:

public FileSystemXmlApplicationContext(
        String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
        throws BeansException {

    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}

2.1 设置资源加载器和资源定位

  通过分析 FileSystemXmlApplicationContext的源代码可以知道,在创建FileSystemXmlApplicationContext容器时,构造方法做以下两项重要工作:

首先,调用父类容器的构造方法(super(parent)方法)为容器设置好 Bean 资源加载器。

然后,再调用父类 AbstractRefreshableConfigApplicationContext 的
setConfigLocations(configLocations)方法设置Bean定义资源文件的定位路径。

通过追踪FileSystemXmlApplicationContext的继承体系 ,发现其父类的父类AbstractApplicationContext中初始化IOC 容器所做的主要源码如下:

public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
//静态初始化块,在整个容器创建过程中只执行一次
static {
//为了避免应用程序在 Weblogic8.1 关闭时出现类加载异常加载问题,加载 IOC 容
//器关闭事件(ContextClosedEvent)类
ContextClosedEvent.class.getName();
}
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
setParent(parent);
}
//获取一个 Spring Source 的加载器用于读入 Spring Bean 定义资源文件
protected ResourcePatternResolver getResourcePatternResolver() {
//AbstractApplicationContext 继承 DefaultResourceLoader,因此也是一个资源加载器
//Spring 资源加载器,其 getResource(String location)方法用于载入资源
return new PathMatchingResourcePatternResolver(this);
}

}

AbstractApplicationContext构造方法中调用PathMatchingResourcePatternResolver 的构造方法创建 Spring 资源加载器:

public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
//设置 Spring 的资源加载器
this.resourceLoader = resourceLoader;
}

在设置容器的资源加载器之后 , 接下来FileSystemXmlApplicationContext 执行setConfigLocations 方法通过调用其父类 AbstractRefreshableConfigApplicationContext 的方法进行对 Bean 定义资源文件的定位,该方法的源码如下:

//处理单个资源文件路径为一个字符串的情况
public void setConfigLocation(String location) {
//String CONFIG_LOCATION_DELIMITERS = ",; /t/n";
//即多个资源文件路径之间用” ,; \t\n”分隔,解析成数组形式
setConfigLocations(StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS));
}
//解析 Bean 定义资源文件的路径,处理多个资源文件字符串数组
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++) {
        // resolvePath 为同一个类中将字符串解析为路径的方法
        this.configLocations[i] = resolvePath(locations[i]).trim();
        }
    }
    else {
       this.configLocations = null;
    }
}

通过这两个方法的源码我们可以看出,我们既可以使用一个字符串来配置多个 Spring Bean 定义资源文件,也可以使用字符串数组,即下面两种方式都是可以的:
  a:ClasspathResourceres=new ClasspathResource(“a.xml,b.xml,……”);
多个资源文件路径之间可以是用” , ; \t\n”等分隔。
  b:ClasspathResourceres=new ClasspathResource(newString[]{“a.xml”,”b.xml”,……});
至此,SpringIOC 容器在初始化时将配置的 Bean 定义资源文件定位为 Spring 封装的 Resource。

2.2 AbstractApplicationContext 的 refresh 函数载入Bean定义过程

SpringIOC 容器对 Bean定义资源的载入是从refresh()函数开始的,refresh()是一个模板方法,refresh()方法的作用是:在创建 IOC 容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在 refresh 之后使用的是新建立起来的 IOC 容器。refresh 的作用类似于对 IOC容器的重启,在新建立好的容器中对容器进行初始化,对 Bean 定义资源进行载入 FileSystemXmlApplicationContext通过调用其父类AbstractApplicationContext的refresh()函数启动整个 IOC 容器对 Bean 定义的载入过程:

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        //调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识
        prepareRefresh();
        //告诉子类启动 refreshBeanFactory()方法,Bean 定义资源文件的载入从
        //子类的 refreshBeanFactory()方法启动
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        //为 BeanFactory 配置容器特性,例如类加载器、事件处理器等
        prepareBeanFactory(beanFactory);
     try {
        //为容器的某些子类指定特殊的 BeanPost 事件处理器
        postProcessBeanFactory(beanFactory);
        //调用所有注册的 BeanFactoryPostProcessor 的 Bean
        invokeBeanFactoryPostProcessors(beanFactory);
        //为 BeanFactory 注册 BeanPost 事件处理器.
        //BeanPostProcessor 是 Bean 后置处理器,用于监听容器触发的事件
        registerBeanPostProcessors(beanFactory);
        //初始化信息源,和国际化相关.
        initMessageSource();
        //初始化容器事件传播器.
        initApplicationEventMulticaster();
        //调用子类的某些特殊 Bean 初始化方法
        onRefresh();
        //为事件传播器注册事件监听器.
        registerListeners();
        //初始化所有剩余的单例 Bean
        finishBeanFactoryInitialization(beanFactory);
        //初始化容器的生命周期事件处理器,并发布容器的生命周期事件
        finishRefresh();
    }
    catch (BeansException ex) {
        if (logger.isWarnEnabled()) {
        logger.warn("Exception encountered during context initialization - " +
        "cancelling refresh attempt: " + ex);
        }
        //销毁已创建的 Bean
        destroyBeans();
        //取消 refresh 操作,重置容器的同步标识.
        cancelRefresh(ex);

        throw ex;
    }
    finally {
        resetCommonCaches();
    }
 }
}

refresh()方法主要为 IOC 容器 Bean 的生命周期管理提供条件,Spring IOC 容器载入 Bean 定义资源文件从其子类容器的refreshBeanFactory() 方法启动 , 所以整个refresh() 中“ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();”这句以后代码的都是注册容器的信息源和生命周期事件,载入过程就是从这句代码启动。

  refresh()方法的作用是:在创建IOC容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在refresh之后使用的是新建立起来的 IOC 容器。refresh的作用类似于对IOC容器的重启,在新建立好的容器中对容器进行初始化,对 Bean 定义资源进行载入

#### 2.3 AbstractApplicationContext 的 obtainFreshBeanFactory() 方法调用子类容器的refreshBeanFactory()方法,启动容器载入 Bean 定义资源文件的过程,代码如下:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//这里使用了委派设计模式,父类定义了抽象的 refreshBeanFactory()方法,具体实现调用子类容器的 refreshBeanFactory()方
法
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}

AbstractApplicationContext类中只抽象定义了refreshBeanFactory()方法,容器真正调用的是其子类 AbstractRefreshableApplicationContext 实现的 refreshBeanFactory()方法,方法的源码如下:

    @Override
    protected final void refreshBeanFactory() throws BeansException {
        //如果已经有容器,销毁容器中的bean,关闭容器
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            //创建IOC容器
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            //对IOC容器进行定制化,如设置启动参数,开启注解的自动装配等
            customizeBeanFactory(beanFactory);
            //调用载入Bean定义的方法,主要这里又使用了一个委派模式,在当前类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

在这个方法中,先判断 BeanFactory 是否存在,如果存在则先销毁beans 并关闭 beanFactory,接着创建DefaultListableBeanFactory,并调用loadBeanDefinitions(beanFactory)装载 bean 定义。

2.4 AbstractRefreshableApplicationContext子类的loadBeanDefinitions 方法

AbstractRefreshableApplicationContext中只定义了抽象的loadBeanDefinitions方法,容器真正调用的是其子类AbstractXmlApplicationContext 对该方法的实现 ,AbstractXmlApplicationContext的主要源码如下:

@Override
    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        //创建XmlBeanDefinitionReader,即创建Bean读取器,并通过回调设置到容器中去,容  器使用该读取器读取Bean定义资源
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // Configure the bean definition reader with this context's
        // resource loading environment.
        //为Bean读取器设置Spring资源加载器,AbstractXmlApplicationContext的
        //祖先父类AbstractApplicationContext继承DefaultResourceLoader,因此,容器本身也是一个资源加载器
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        //为Bean读取器设置SAX xml解析器
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        //当Bean读取器读取Bean定义的Xml资源文件时,启用Xml的校验机制
        initBeanDefinitionReader(beanDefinitionReader);
        //Bean读取器真正实现加载的方法
        loadBeanDefinitions(beanDefinitionReader);
    }

    protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
            reader.setValidating(this.validating);
    }


  //Xml Bean读取器加载Bean定义资源
 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        //获取Bean定义资源的定位
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            //Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位
            //的Bean定义资源
            reader.loadBeanDefinitions(configResources);
        }
        //如果子类中获取的Bean定义资源定位为空,则获取FileSystemXmlApplicationContext构造方法中setConfigLocations方法设置的资源
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            //Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位
            //的Bean定义资源
            reader.loadBeanDefinitions(configLocations);
        }
  }

//这里又使用了一个委托模式,调用子类的获取Bean定义资源定位的方法
//该方法在ClassPathXmlApplicationContext中进行实现,对于我们
//举例分析源码的FileSystemXmlApplicationContext没有使用该方法
@Nullable
protected Resource[] getConfigResources() {
    return null;
}   

XmlBean 读取器(XmlBeanDefinitionReader)调用其父类AbstractBeanDefinitionReader的reader.loadBeanDefinitions方法读取Bean定义资源。由于我们使用 FileSystemXmlApplicationContext作为例子分析,因此getConfigResources的返回值为null,因此程序执行reader.loadBeanDefinitions(configLocations)分支。

由于IOC 容器的初始化内容比较多一次文章无法写完,所以分了几篇进行讲解此篇为第一篇。

文档有参考其他资料,如果问题请联系我,进行删除!

posted on 2018-05-22 16:36  未亦末  阅读(338)  评论(0编辑  收藏  举报