Spring IOC源码分析


IOC简要解释

在应用开发中,开发人员往往需要引用和调用其它组件的服务,这种依赖关系如果固化在组件设计中,就会造成依赖关系的僵化和以后维护成本的增加。如果使用IoC容易,把资源的获取反转,具体相对java来说就是把bean的依赖关系交给IoC容易来处理。在反转实现中,具体通过可读的文件来配置。并且bean的耦合关系需要变动时候,可以不用重新改变或者编译源代码就可以实现,这也符合设计原则中的开闭原则,能够提供组件系统设计的灵活性。基于此,我们下面来简要分析一下Spring IoC相关的源码

ApplicationContext

spring容器uml类图
通过上面的类图,可以看到ApplicationContext是基于BeanFactory接口扩展来的,但它还继承了一些其它的接口,所以它是一个Spring提供给我们的一个更高级的IoC容易。
下面我们来分析一下ApplicationContext启动部分的源码。我们看到
AbstractApplicationContext.refresh

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}

基于上面的ClassPathXmlApplicationContext可以看到它会调用AbstractApplicationContext的refresh方法,这个也是IoC启动的核心代码,下面就来看下。
我们可以看到refresh方法里面又包含很多步骤,我们主要关注一下红线标记出来的那个方法,它就是Spring IoC的初始化。

refresh的时序图

IoC加载时序图

通过上面的时序图,我们可以看到ioc启动大致可以分为3个过程。

  • resource的定位过程,可以简单理解就是找到我们定义bean的相关的xml文件
  • BeanDifinition的载入,就是把用户在xml中定义好的bean解析成为IoC的内部数据结构,也就是BeanDifinition。
  • 向IoC容易注册这些BeanDifinition,把BeanDifinition注册到一个Map中保存。

下面我们以上面的时序图为基础,分别对上面的3个过程简要分析一下。

resource的定位

  • ClassPathXmlApplicationContext的构造方法setConfigLocations(configLocations);这个最终设置了AbstractRefreshableConfigApplicationContext的configLocations属性
  • AbstractXmlApplicationContext.loadBeanDefinitions()方法会调用到XmlBeanDefinitionReader.loadBeanDefinitions(configLocations);
  • XmlBeanDefinitionReader.doLoadBeanDefinitions(EncodedResource encodedResource)这就把一开始传入的一个指向xml的路径变成了一个程序可以读的encodedResource,就完成了resource的定位。

BeanDifinition的载入和解析

  • XmlBeanDefinitionReader.doLoadBeanDefinitions(InputSource inputSource, Resource resource)这个方法把资源封装成一个Document
  • DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)这个方法解析document中的每一个获取到的标签
    xml bean的解析
  • 最终通过BeanDefinitionParserDelegate..parseBeanDefinitionElement(Element)来解析Element,并用BeanDefinitionHolder来持有BeanDifinition。
    beanDifinitionHolder

BeanDifinition在IoC容器中的注册

  • BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());其中这个getReaderContext().getRegistry()返回的就是最早创建的DefaultListableBeanFactory
  • DefaultListableBeanFactory.registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
  • DefaultListableBeanFactory持有下面这个对象
    private final Map beanDefinitionMap = new ConcurrentHashMap(256);每次基本都是putIfAbsent模式以放到beanDefinitionMap,这样就完成了bean的注册过程。

IoC容器的依赖注入

上面3个过程只是完成了每个单独在xml中定义的bean在IoC容器中的注册,但是我们知道很多bean最终的实例化都要依赖其它bean。然而这个依赖注入我们从上面的代码来看,并没有完成这个过程。事实上,依赖注入是在refresh中的

// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);

上述方法会调用到getBean(从上面代码的注释可以看出它只能实例化没有设置lazy-init的bean,如果设置了lazy-init只能在最终applicationContext.getbean的时候完成依赖注入)这个getbean会通过判断当前bean是否有depends-on的bean,若果有,则对依赖的depend-on的bean在递归调用get Bean。最终完成bean的依赖注入。


文章作者: 叶明
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 叶明 !
评论
 上一篇
Spring IOC源码分析二(Bean实例化) Spring IOC源码分析二(Bean实例化)
AbstractApplication.refresh如下图,上一篇文章我们已经提到过refresh方法,spring容器启动的一系列步骤都是这个方法来实现的。上一篇文章我们主要基于obtainFreshBeanFactory()这个方法来
2016-04-26
下一篇 
netty源码研究二(请求处理) netty源码研究二(请求处理)
前言上一篇文章中,已经分析了netty的服务器端启动过程,我们知道NioEventLoop.run()方法,是服务器端用来轮询客户端的请求。我们指定创建出的NioServerSocketChannel就是注册到了NioEventLoop中的
2016-04-02
  目录