啃啃老菜:Spring IOC核心源码学习(一)

本文主要以spring ioc容器基本代码骨架为切入点,理解ioc容器的基本代码组件结构,各代码组件细节剖析将放在后面的学习文章里。

关于IOC容器

IoC容器:最主要是完成了完成对象的创建和依赖的管理注入等等。

先从我们自己设计这样一个视角来考虑:

所谓控制反转,就是把原先我们代码里面需要实现的对象创建、依赖的代码,反转给容器来帮忙实现。那么必然的我们需要创建一个容器,同时需要一种描述来让容器知道需要创建的对象与对象的关系。这个描述最具体表现就是我们可配置的文件。

对象和对象关系怎么表示?

可以用xml,properties文件等语义化配置文件表示。

描述对象关系的文件存放在哪里?

可能是classpath,filesystem,或者是URL网络资源,servletContext等。

回到正题,有了配置文件,还需要对配置文件解析。

不同的配置文件对对象的描述不一样,如标准的,自定义声明式的,如何统一? 在内部需要有一个统一的关于对象的定义,所有外部的描述都必须转化成统一的描述定义。

如何对不同的配置文件进行解析?需要对不同的配置文件语法,采用不同的解析器。

基于以上问题,对应过来,刚好是 spring ioc 容器抽象的的几个主要接口:

Resource

BeanDefinition

BeanDefinitionReader

BeanFactory

ApplicationContext

以上五个都是接口,都有各式各样的实现,正是这5个接口定义了spring ioc容器的基本代码组件结构。而其组件各种实现的组合关系组成了一个运行时的具体容器。

各代码组件详解

1.Resource

是对资源的抽象,每一个接口实现类都代表了一种资源类型,如ClasspathResource、URLResource,FileSystemResource等。每一个资源类型都封装了对某一种特定资源的访问策略。它是spring资源访问策略的一个基础实现,应用在很多场景。

具体可以参考文章:

Spring 资源访问剖析和策略模式应用

http://www.ibm.com/developerworks/cn/java/j-lo-spring-resource/index.html

2.BeanDefinition

用来抽象和描述一个具体bean对象。是描述一个bean对象的基本数据结构。

3.BeanDefinitionReader

BeanDefinitionReader将外部资源对象描述的bean定义统一转化为统一的内部数据结构BeanDefinition。对应不同的描述需要有不同的Reader。如XmlBeanDefinitionReader用来读取xml描述配置的bean对象。

4.BeanFactory

用来定义一个很纯粹的bean容器。它是一个bean容器的必备结构。同时和外部应用环境等隔离。BeanDefinition是它的基本数据结构。它维护一个BeanDefinitions Map,并可根据BeanDefinition的描述进行bean的创建和管理。

5.ApplicationContext

从名字来看叫应用上下文,是和应用环境息息相关的。没错这个就是我们平时开发中经常直接使用打交道的一个类,应用上下文,或者也叫做spring容器。其实它的基本实现是会持有一个BeanFactory对象,并基于此提供一些包装和功能扩展。为什么要这么做呢?因为BeanFactory实现了一个容器基本结构和功能,但是与外部环境隔离。那么读取配置文件,并将配置文件解析成BeanDefinition,然后注册到BeanFactory的这一个过程的封装自然就需要ApplicationContext。ApplicationContext和应用环境细细相关,常见实现有ClasspathXmlApplicationContext,FileSystemXmlApplicationContext,WebApplicationContext等。Classpath、xml、FileSystem、Web等词都代表了应用和环境相关的一些意思,从字面上不难理解各自代表的含义。

当然ApplicationContext和BeanFactory的区别远不止于此,有:

1.  资源访问功能:在Resource和ResourceLoader的基础上可以灵活的访问不同的资源。

2.  支持不同的信息源。

3.  支持应用事件:继承了接口ApplicationEventPublisher,这样在上下文中为bean之间提供了事件机制。

……

以上5个组件基本代表了ioc容器的一个最基本组成,而组件的组合是放在ApplicationContext的实现这一层来完成。

以ClasspathXmlApplicationContext 容器实现为例,其组合关系如下:

ClassPathXmlApplicationContext的refresh() 方法负责完成了整个容器的初始化。

为什么叫refresh?也就是说其实是刷新的意思,该IOC容器里面维护了一个单例的BeanFactory,如果bean的配置有修改,也可以直接调用refresh方法,它将销毁之前的BeanFactory,重新创建一个BeanFactory。所以叫refresh也是能理解的。

以下是Refresh的基本步骤:
1.把配置xml文件转换成resource。resource的转换是先通过ResourcePatternResolver来解析可识别格式的配置文件的路径

(如"classpath*:"等),如果没有指定格式,默认会按照类路径的资源来处理。 

2.利用XmlBeanDefinitionReader完成对xml的解析,将xml Resource里定义的bean对象转换成统一的BeanDefinition。

3.将BeanDefinition注册到BeanFactory,完成对BeanFactory的初始化。BeanFactory里将会维护一个BeanDefinition的Map。


当getBean的时候就会根据调用BeanFactory,根据bean的BeanDifinition来实例化一个bean。当然根据bean的lazy-init、protetype等属性设置不同以上过程略有差别。

refresh()代码如下:

Java代码 
  1. public void refresh() throws BeansException, IllegalStateException {
  2. synchronized (this.startupShutdownMonitor) {
  3. // Prepare this context for refreshing.
  4. prepareRefresh();
  5. // Tell the subclass to refresh the internal bean factory.
  6. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  7. // Prepare the bean factory for use in this context.
  8. prepareBeanFactory(beanFactory);
  9. try {
  10. // Allows post-processing of the bean factory in context subclasses.
  11. postProcessBeanFactory(beanFactory);
  12. // Invoke factory processors registered as beans in the context.
  13. invokeBeanFactoryPostProcessors(beanFactory);
  14. // Register bean processors that intercept bean creation.
  15. registerBeanPostProcessors(beanFactory);
  16. // Initialize message source for this context.
  17. initMessageSource();
  18. // Initialize event multicaster for this context.
  19. initApplicationEventMulticaster();
  20. // Initialize other special beans in specific context subclasses.
  21. onRefresh();
  22. // Check for listener beans and register them.
  23. registerListeners();
  24. // Instantiate all remaining (non-lazy-init) singletons.
  25. finishBeanFactoryInitialization(beanFactory);
  26. // Last step: publish corresponding event.
  27. finishRefresh();
  28. }
  29. catch (BeansException ex) {
  30. // Destroy already created singletons to avoid dangling resources.
  31. beanFactory.destroySingletons();
  32. // Reset 'active' flag.
  33. cancelRefresh(ex);
  34. // Propagate exception to caller.
  35. throw ex;
  36. }
  37. }
  38. }

以上的obtainFreshBeanFactory是很关键的一个方法,里面会调用loadBeanDefinition方法,如下:

Java代码 
  1. protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
  2. // Create a new XmlBeanDefinitionReader for the given BeanFactory.
  3. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
  4. // Configure the bean definition reader with this context's
  5. // resource loading environment.
  6. beanDefinitionReader.setResourceLoader(this);
  7. beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
  8. // Allow a subclass to provide custom initialization of the reader,
  9. // then proceed with actually loading the bean definitions.
  10. initBeanDefinitionReader(beanDefinitionReader);
  11. loadBeanDefinitions(beanDefinitionReader);
  12. }

LoadBeanDifinition方法很关键,这里特定于整个IOC容器,实例化了一个XmlBeanDefinitionReader来解析Resource文件。关于Resource文件如何初始化和xml文件如何解析都在

Java代码 
  1. loadBeanDefinitions(beanDefinitionReader);

里面的层层调用完成,这里不在累述。

小结

Spring的扩展性是毋庸置疑的,学习spring的设计是一个很好的实践理论结合。主要个人觉得有几点:

1.  框架顶层的设计有着很好的抽象,遵循面向接口编程的规范。Resource、BeanFactory、ApplicationContext都是非常好的接口抽象,非常明确的定义了该组件的一些功能。

2.  利用组合模式。

3.  个组件的实现里大量使用了模板方法模式,提升了同一组件代码的复用性。

4.  各种设计保留了扩展的接口,很多基于spring的框架都可以很容易的介入实现了自己的一些扩展。

5.  框架里采用里很多经典的设计模式,如代理、装饰、策略等等。

啃啃老菜:Spring IOC核心源码学习(一)的更多相关文章

  1. Spring IOC核心源码学习

    1. 初始化 大致单步跟了下Spring IOC的初始化过程,整个脉络很庞大,初始化的过程主要就是读取XML资源,并解析,最终注册到Bean Factory中: 在完成初始化的过程后,Bean们就在B ...

  2. 手撕spring核心源码,彻底搞懂spring流程

    引子 十几年前,刚工作不久的程序员还能过着很轻松的日子.记得那时候公司里有些开发和测试的女孩子,经常有问题解决不了的,不管什么领域的问题找到我,我都能帮她们解决.但是那时候我没有主动学习技术的意识,只 ...

  3. Android版数据结构与算法(五):LinkedHashMap核心源码彻底分析

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 上一篇基于哈希表实现HashMap核心源码彻底分析 分析了HashMap的源码,主要分析了扩容机制,如果感兴趣的可以去看看,扩容机制那几行最难懂的 ...

  4. 3 手写Java HashMap核心源码

    手写Java HashMap核心源码 上一章手写LinkedList核心源码,本章我们来手写Java HashMap的核心源码. 我们来先了解一下HashMap的原理.HashMap 字面意思 has ...

  5. 手写 Java HashMap 核心源码

    手写 Java HashMap 核心源码 手写 Java HashMap 核心源码 上一章手写 LinkedList 核心源码,本章我们来手写 Java HashMap 的核心源码. 我们来先了解一下 ...

  6. Java内存管理-掌握类加载器的核心源码和设计模式(六)

    勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇文章介绍了类加载器分类以及类加载器的双亲委派模型,让我们能够从整体上对类加载器有 ...

  7. Spring IOC 容器源码分析 - 余下的初始化工作

    1. 简介 本篇文章是"Spring IOC 容器源码分析"系列文章的最后一篇文章,本篇文章所分析的对象是 initializeBean 方法,该方法用于对已完成属性填充的 bea ...

  8. Spring IOC 容器源码分析 - 填充属性到 bean 原始对象

    1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的.我在前面几篇文章中介绍过 Spring 创建 bean 的流程,即 Spring 先通过反 ...

  9. Spring IOC 容器源码分析 - 循环依赖的解决办法

    1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的.在本篇文章中,我会首先向大家介绍一下什么是循环依赖.然后,进入源码分析阶段.为了更好的说明 Spring 解决循环依赖的办法,我 ...

  10. Spring IOC 容器源码分析 - 创建原始 bean 对象

    1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续.在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程.本篇文章,我们就从战术的层面上,详细分析doCreat ...

随机推荐

  1. 英文短语和单词备忘 - as well as

    English phrases and vocabulary notes: as well as "as well as" 是一个连接词组,通常用于连接两个相似的元素,以强调两者都 ...

  2. Go日志管理库zap

    一.zap介绍 在许多Go语言项目中,我们需要一个好的日志记录器能够提供下面这些功能: 1.能够将事件记录到文件中,而不是应用程序控制台. 2.日志切割-能够根据文件大小.时间或间隔等来切割日志文件. ...

  3. Figma 学习笔记 – Variants

    参考 Create and use variants 定义与用途 Variants 是 Component 的扩展使用方式. 它就像 HTML 元素的属性一样, 通过修改属性, 元素就会变成相应的样式 ...

  4. Spring —— 事务角色&&事务属性

    事务角色 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法       事务属性:    事务传播行 ...

  5. MVC模式与三层架构

    MVC 模式    三层架构    MVC 模式 与 三层架构 的关系   

  6. springboot 基本配置文件

    spring.datasource.url=jdbc:mysql://127.0.0.1:3306/game?useUnicode=true&zeroDateTimeBehavior=conv ...

  7. FB 汉化版恢复慢速更新

    汉化版将恢复慢速更新 -------------------------[Q群]------------------------ 1 群: 11675361 2 群: 47642719 ------- ...

  8. WeiXin.Export.20220726

    用 QuestPDF操作生成PDF更快更高效! Blazor Server 应用程序中进行 HTTP 请求 开源WPF控件库-AdonisUI FastTunnel-开源内网穿透框架 AI 之 Ope ...

  9. USB总线-Linux内核USB3.0设备控制器中断处理程序分析(九)

    1.概述 USB设备枚举.请求处理.数据交互都涉及USB设备控制器中断.当有事件发生时,USB设备控制器首先将事件信息通过DMA写入到事件缓冲区中,然后向CPU发出中断,随后CPU调用中断处理函数开始 ...

  10. 数据库日常实操优质文章分享(含Oracle、MySQL等) | 2023年2月刊

    本文为大家整理了墨天轮数据社区2023年2月发布的优质技术文章,主题涵盖Oracle.MySQL.PostgreSQL等数据库的环境搭建.故障处理等日常实践操作,以及概念梳理.常用脚本等总结记录,分享 ...