spring技术内幕读书笔记之IoC容器的学习

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring技术内幕读书笔记之IoC容器的学习相关的知识,希望对你有一定的参考价值。

第二篇:IoC容器的初始化

在介绍FileSystemXmlApplicationContext 的例子时有说到IoC容器的初始化由refresh()方法开始启动,此方法标志着IoC容器的启动[构造器中有此方法的容器]。细分的话,这个启动过程具体的可分为三部分:

第一部分:BeanDefinition的Resource定位

是指BeanDefinition(容器内部定义的bean的数据结构),它由ResourceLoader通过统一的Resource接口来完成,Resource对各种形式的BeanDefinition的使用都提供了统一的接口,比如文件系统中的Bean定义信息可以通过FileSystemResource进行抽象,类路径中的Bean定信息可以通过ClassPathResource来获取。

在介绍XmlBeanFactory时可以看到,使用DefaultListableBeanFactory时,首先要定义一个Resource(即ClassPathResourceres = new ClassPathResource("SpringBeans.xml");)这里定义的Resource不能由DefaultListableBeanFactory直接使用,而是通过初始化一个XmlBeanDefinitionReader,靠reader对象来处理,完成定位的。

再拿FileSystemXmlApplicationContext的例子与之比较,不难发现ApplicationContext相对于直接使用DefaultListBeanFactory有一个好处----在ApplicationContext中Spring已经为我们提供了一系列加载不同Resource的读取器的实现。具体的读取器是怎样实现的正是此部分研究的重点。

还是那句话IoC容器的初始化由refresh()方法开始启动,FileSystemXmlApplicationContext在构造器中调用了refresh方法,在refresh方法中会调用obtainFreshBeanFactory获取beanFactory,在obtainFreshBeanFactory中会调用refreshBeanFactory这一抽象方法,在此方法的实现过程中构造了DefaultListableBeanFactory,通过XmlBeanFactory的例子我们知道DefaultListableBeanFactory初始化一个XmlBeanDefinitionReader,靠reader对象来处理,完成定位的。

reader对象有了之后,该怎样读入呢?

XmlBeanFactory是这样

public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
    super(parentBeanFactory);
    this.reader.loadBeanDefinitions(resource);
}

这里同样需要调用loadBeanDefinitions方法,但resource是通过调用重写getResourceByPath获得的。子类FileSystemXmlApplicationContext重写了此方法会返回FileSystemResource对象,其他类型的ApplicationContext会对应生成其他类型的Resource。

技术分享

这个FileSystemXmlApplicationContext的Resource定位用一句话概括就是:refresh()之后,构造出XmlBeanDefinitionReader对象,在loadBeanDefinitions时根据FileSystemXmlApplicationContext重写getResourceByPath返回的以FileSystem方式存在的resource对象进行BeanDefinition的载入,在定位完成之后知识为载入提供了I/O操作条件,并未开始读入。

第二部分:BeanDefinition的载入和解析

这个过程就是把用户定义好的Bean表示成IoC容器内部的数据结构即BeanDefinition。首先需要知道IoC容器对Bean的管理和依赖注入功能的实现,是通过对其持有的BeanDefinition进行各种相关操作来完成的。这些BeanDefinition数据在IoC容器中通过一个HashMap来保持和维护。当然这只是一种比较简单的维护方式,如果需要提高IoC容器的性能和容量,完全可以自己做一些扩展。

下面,从DefaultListableBeanFactory的设计入手,看看IoC容器是怎样完成BeanDefinition载入的。

首先是初始化的入口—refresh方法,该方法在FileSystemXmlApplicationContext的构造方法中被调用,在FileSystemXmlApplicationContext的基类AbstractApplicationContext中实现,它详细地描述了整个ApplicationContext的初始化过程,比如BeanFactory的更新,MessageSource和PostProcessor的注册等。这里看起来更像是对ApplicationContext进行初始化的模块或执行提纲,这个执行过程为Bean的生命周期管理提供了条件。

refresh实现源码:

public void refresh() throwsBeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
  // Prepare this context forrefreshing.
  prepareRefresh();
  // Tell the subclass to refresh the internal bean factory.
  // 这里是在子类中启动refreshBeanFactory()的地方
  ConfigurableListableBeanFactory beanF actory= obtainFreshBeanFactory();
  // Prepare the bean factory for use in this context.
  prepareBeanFactory(beanFactory);
  try {
      // 设置BeanFactory的后置处理
    // Allows post-processing ofthe bean factory in context subclasses.
    postProcessBeanFactory(beanFactory);
    // 调用BeanFactory的后处理器,这些后处理器是在Bean定义中向容器注册的
    // Invoke factory processorsregistered as beans in the context.
    invokeBeanFactoryPostProcessors(beanFactory);
    // 注册Bean的后处理器,在Bean创建过程中调用
    // Register bean processorsthat intercept bean creation.
    registerBeanPostProcessors(beanFactory);
    // 对上下文中的消息源进行初始化
    // Initialize message sourcefor this context.
    initMessageSource();
    // 初始化上下文中的事件机制
    // Initialize event multicasterfor this context.
    initApplicationEventMulticaster();
    // 初始化其他的特殊Bean
    // Initialize other specialbeans in specific context subclasses.
    onRefresh();
    // 检查监听Bean并且将这些Bean向容器注册
    // Check for listener beans andregister them.
    registerListeners();
    // 实例化所有的(non-lazy-init)单件
    // Instantiate all remaining(non-lazy-init) singletons.
    finishBeanFactoryInitialization(beanFactory);
    // 发布容器事件,结束Refresh过程
    // Last step: publish correspondingevent.
    finishRefresh();
   }catch (BeansException ex) {
    logger.warn("Exceptionencountered during context initialization - cancelling refresh attempt",ex);
    // 为防止Bean资源占用,在异常处理中,销毁已经在前面过程中生成的单件Bean
    // Destroy already createdsingletons to avoid dangling resources.
    destroyBeans();
    // 重置 ‘active‘标志
    // Reset ‘active‘ flag.
    cancelRefresh(ex);
    // Propagate exception tocaller.
    throw ex;
   }
 }
}

在ConfigurableListableBeanFactory beanFactory =obtainFreshBeanFactory();中会调用AbstractRefreshableApplicationContext中的refreshBeanFactory方法,在这个方法中创建了BeanFactory。在创建IoC容器前,如果已经有容器存在,那么需要把已有的容器销毁和关闭,保证在refresh以后使用的是新建立起来的IoC容器。在建好容器后开始BeanDefinition的载入工作。

具体的refreshBeanFactory实现:

protected final void refreshBeanFactory()throws BeansException {
    // 这里判断,如果已经建立了BeanFactory,则销毁并关闭该BeanFactory
  if (hasBeanFactory()) {
    destroyBeans();
    closeBeanFactory();
  }
  // 这里是创建并设置持有的DefaultListableBeanFactory的地方
  // 同时调用loadBeanDefinitions载入BeanDefinition的信息
  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 beandefinition source for " + getDisplayName(), ex);
  }
 }

BeanDefinition的载入交互过程:

技术分享

这里调用的loadBeanDefinitions实际上是一个抽象方法,那么实际的载入过程发生在哪里呢?我们看看前面提到的loadBeanDefinitions在AbstractRefreshableApplicationContext的子类AbstractXmlApplicationContext中的实现,在这个loadBeanDefinitions中,初始化了读取器XmlBeanDefinitionReader,然后把这个读取器在IoC容器中设置好(过程和编程式使用XmlBeanFactory是类似的),最后是启动读取器来完成BeanDefinition在IoC容器的载入。

// 这里是实现loadBeanDefinitions的地方
protected void loadBeanDefinitions(DefaultListableBeanFactorybeanFactory) throws BeansException, IOException {
    // Create a new XmlBeanDefinitionReader for the given BeanFactory.
  // 创建XmlBeanDefinitionReader,并通过回调设置到BeanFactory中去,创建BeanFactory
  // 的过程可以参考上文对编程式使用IoC容器的相关分析,这里和前面一样,使用的也是
  // DefaultListableBeanFactory
  XmlBeanDefinitionReader beanDefinitionReader = newXmlBeanDefinitionReader(beanFactory);
  // Configure the bean definition reader with this context‘s
  // resource loading environment.
  // 这里设置XmlBeanDefinitionReader,为XmlBeanDefinitionReader配
  // ResourceLoader,因为DefaultResourceLoader是父类,所以this可以直接被使用
  beanDefinitionReader.setEnvironment(this.getEnvironment());
  beanDefinitionReader.setResourceLoader(this);
  beanDefinitionReader.setEntityResolver(newResourceEntityResolver(this));
  // Allow a subclass to provide custom initialization of the reader,
  // then proceed with actually loading the bean definitions.
  // 这是启动Bean定义信息载入的过程
  initBeanDefinitionReader(beanDefinitionReader);
  loadBeanDefinitions(beanDefinitionReader);
}
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader){
       reader.setValidating(this.validating);
}

接着就是loadBeanDefinitions调用的地方,首先得到BeanDefinition信息的Resource定位,然后直接调用XmlBeanDefinitionReader来读取,具体的载入过程是委托给BeanDefinitionReader完成的。因为这里的BeanDefinition是通过XML文件定义的,所以这里使用XmlBeanDefinitionReader来载入BeanDefinition到容器中。

protected voidloadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException,IOException {
    // 以Resource的方式获得配置文件的资源位置
  Resource[] configResources = getConfigResources();
  if (configResources != null) {
      reader.loadBeanDefinitions(configResources);
  }
  // 以String的形式获得配置文件的定位
  String[] configLocations = getConfigLocations();
  if (configLocations != null) {
    reader.loadBeanDefinitions(configLocations);
  }
}
protected Resource[] getConfigResources() {
  return null;
}

通过以上对实现原理的分析,我们可以看到,在初始化FileSystemXmlApplicationContext的过程中是通过调用IoC容器的refresh来启动整个BeanDefinition的载入过程的,这个初始化是通过定义的XmlBeanDefinitionReader来完成的。同时,我们也知道实际使用的IoC容器是DefualtListableBeanFactory,具体的Resource载入在XmlBeanDefinitionReader读入BeanDefinition时实现

因为Spring可以对应不同形式的BeanDefinition。由于这里使用的是XML方式的定义,所以需要使用XmlBeanDefinitionReder。如果使用了其他的BeanDefinition方式,就需要使用其他种类的BeanDefinitionReder来完成数据的载入工作。

在XmlBeanDefinitionReader的实现中可以看到,是在reader.loadBeanDefinitions中开始进行BeanDefinition的载入的,而这时XmlBeanDefinitionReader的父类AbstractBean-Definition-Reader已经为BeanDefinition的载入做好了准备。

@Override
publicint loadBeanDefinitions(Resource... resources) throwsBeanDefinitionStoreException {
    Assert.notNull(resources,"Resource array must not be null");
  intcounter = 0;
  for(Resource resource : resources) {
    counter+= loadBeanDefinitions(resource);
  }
  returncounter;
}

这里调用的loadBeanDefinitions(resource);在AbstractBeanDefinitionReader中没有实现,它是一个接口方法,具体实现在XmlBeanDefinitionReader中。在拥有了文件对象后就可以按照spring的bean定义规则来对XML文档树进行解析了,如何得到的document对象,可以参考DefaultDocumentLoader。紧接着Spring的BeanDefinition如何按照bean语义进行解析转化成容器内部数据结构的,这部分在registerBeanDefinition(doc,resource)中完成。

loadBeanDefinitionsà doLoadBeanDefinitionsà registerBeanDefinitions

至于怎样详细转化的不再深入了。

第三部分:向IoC容器注册这些BeanDefinition

这个过程是通过BeanDefinitionRegistry接口把载入过程中解析得到的BeanDefinition项IoC容器注册,IoC容器内部将这些BeanDefinition注入到HashMap中,IoC容器就是通过这个hashmap来持有这些BeanDefinition数据的。可以看到HashMap定义在DefaultBeanFactory中。

private static final Map<String,Reference<DefaultListableBeanFactory>> serializableFactories =

new ConcurrentHashMap<String,Reference<DefaultListableBeanFactory>>(8);

具体的注册调用过程为:

技术分享

技术分享

从上图的调用关系可以看到registerBeanDefinition方法何时被调用。

注意:这个初始化过程不包括Bean依赖注入的实现,依赖注入一般发生在应用第一次通过getBean向容器索取Bean的时候,可以通过设置Bean的lazyinit属性使得bean的依赖注入在IoC容器初始化是就完成。


以上是关于spring技术内幕读书笔记之IoC容器的学习的主要内容,如果未能解决你的问题,请参考以下文章

《Spring技术内幕》笔记-第二章 IoC容器的实现

Spring技术内幕阅读笔记

Spring学习笔记

Spring in Action读书笔记——容器和依赖注入

手撸golang 仿spring ioc/aop 之4 蓝图

手撸golang 仿spring ioc/aop 之12 增强3