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

一,概述

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

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。咱们能够根据下面的代码跟进 去,去理解定位、载入、注册的全过程。app

// 根据 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 对象ide

下面我给出本身跟进去的时序图里面标红的为主要的方法须要仔细看:
这里写图片描述
具体类这里不作详细的讲解,下面FileSystemXmlApplicationContext 容器建立会对重要方法作详细说明。函数

二,FileSystemXmlApplicationContext IOC容器建立的流程

ApplicationContext = new FileSystemXmlApplicationContext(xmlPath);post

先看其构造函数的调用:this

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

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

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 容器的初始化内容比较多一次文章没法写完,因此分了几篇进行讲解此篇为第一篇。

文档有参考其余资料,若是问题请联系我,进行删除!

相关文章
相关标签/搜索