Spring源码系列一:临摹

目录

  • 写在前面
  • 一、IOC容器的组成和实现
    • 1.1 Resource
    • 1.2 BeanDefinition
    • 1.3 BeanFactory
    • 1.4 ApplicationContext
  • 二、IOC容器的初始化
    • 2.1 ApplicationContext 的构造和执行
    • 2.2 BeanFactory 的构造与执行
    • 2.3 tiny-spring 总体流程的分析
  • 三、AOP的植入和实现细节
    • 3.1在 Bean 初始化过程中完成 AOP 的植入
    • 3.2 AOP 中动态代理的实现步骤
  • 四、好问题

碎碎念

可能真的是处于一个比较尴尬的年龄和尴尬的时期了,再加上有一些不如意的事情,发现自己愈发的变得堕落和紧张,但自己唯一能做的事情,就是耐下性子学习了,听的再多,不如去多做几件事情,这是我给自己的要求,也希望自己接下来的半年能有所收获和进步。


写在前面

下决心要开始读spring的源码,但spring项目的代码实在是太繁琐了,然后很幸运搜到了一篇文章,文章中提到了这个tiny-spring,惊喜,这就是我最喜欢的一种方式:先实现一个最简版,然后一步一步丰富它。

”有人把程序员与画家做比较,画家有门基本功叫临摹,tiny-spring可以算是一个程序的临摹版本-从自己的需求出发,进行程序设计,同时对著名项目进行参考"
这是tiny-spring首页中的一句话,也是特别能打动我的一句话,是呀,我们程序员应该也要有临摹这个基本功:临摹优秀的框架;如果我们不能一步到位体会到这个框架的精髓,那我们可以一点点的按照功能拆解和模仿,总有一天,会慢慢感悟和掌握。

源码地址:https://github.com/code4craft/tiny-spring


一、IOC容器的组成和实现

1.1 Resource

以 Resource 接口为核心发散出的几个类,都是用于解决 IoC 容器中的内容从哪里来的问题,也就是配置文件从哪里读取、配置文件如何读取的问题。

类名 说明
Resource 接口,标识一个外部资源。通过 getInputStream() 方法 获取资源的输入流 。
UrlResource 实现 Resource 接口的资源类,通过 URL 获取资源。
ResourceLoader 资源加载类。通过 getResource(String) 方法获取一个 Resouce 对象,是 获取 Resouce 的主要途径 。

注: 这里在设计上有一定的问题,ResourceLoader 直接返回了一个 UrlResource,更好的方法是声明一个 ResourceLoader 接口,再实现一个 UrlResourceLoader 类用于加载 UrlResource。

1.2 BeanDefinition

以 BeanDefinition 类为核心发散出的几个类,都是用于解决 Bean 的具体定义问题,包括 Bean 的名字是什么、它的类型是什么,它的属性赋予了哪些值或者引用,也就是 如何在 IoC 容器中定义一个 Bean,使得 IoC 容器可以根据这个定义来生成实例的问题。

类名 说明
BeanDefinition 该类保存了 Bean 定义。包括 Bean 的 名字 String beanClassName、类型 Class beanClass、属性 PropertyValues propertyValues。根据其 类型 可以生成一个类实例,然后可以把 属性 注入进去。propertyValues 里面包含了一个个 PropertyValue 条目,每个条目都是键值对 String - Object,分别对应要生成实例的属性的名字与类型。在 Spring 的 XML 中的 property 中,键是 key ,值是 value 或者 ref。对于 value 只要直接注入属性就行了,但是 ref 要先进行解析。Object 如果是 BeanReference 类型,则说明其是一个引用,其中保存了引用的名字,需要用先进行解析,转化为对应的实际 Object。
BeanDefinitionReader 解析 BeanDefinition 的接口。通过 loadBeanDefinitions(String) 来从一个地址加载类定义。
AbstractBeanDefinitionReader 实现 BeanDefinitionReader 接口的抽象类(未具体实现 loadBeanDefinitions,而是规范了 BeanDefinitionReader 的基本结构)。内置一个 HashMap rigistry,用于保存 String - beanDefinition 的键值对。内置一个 ResourceLoader resourceLoader,用于保存类加载器。用意在于,使用时,只需要向其 loadBeanDefinitions() 传入一个资源地址,就可以自动调用其类加载器,并把解析到的 BeanDefinition 保存到 registry 中去。
XmlBeanDefinitionReader 具体实现了 loadBeanDefinitions() 方法,从 XML 文件中读取类定义。

1.3 BeanFactory

以 BeanFactory 接口为核心发散出的几个类,都是用于解决 IoC 容器在 已经获取 Bean 的定义的情况下,如何装配、获取 Bean 实例 的问题。

类名 说明
BeanFactory 接口,标识一个 IoC 容器。通过 getBean(String) 方法来 获取一个对象
AbstractBeanFactory BeanFactory 的一种抽象类实现,规范了 IoC 容器的基本结构,但是把生成 Bean 的具体实现方式留给子类实现。IoC 容器的结构:AbstractBeanFactory 维护一个 beanDefinitionMap 哈希表用于保存类的定义信息(BeanDefinition)。获取 Bean 时,如果 Bean 已经存在于容器中,则返回之,否则则调用 doCreateBean 方法装配一个 Bean。(所谓存在于容器中,是指容器可以通过 beanDefinitionMap 获取 BeanDefinition 进而通过其 getBean() 方法获取 Bean。)
AutowireCapableBeanFactory 可以实现自动装配的 BeanFactory。在这个工厂中,实现了 doCreateBean 方法,该方法分三步:1,通过 BeanDefinition 中保存的类信息实例化一个对象;2,把对象保存在 BeanDefinition 中,以备下次获取;3,为其装配属性。装配属性时,通过 BeanDefinition 中维护的 PropertyValues 集合类,把 String - Value 键值对注入到 Bean 的属性中去。如果 Value 的类型是 BeanReference 则说明其是一个引用(对应于 XML 中的 ref),通过 getBean 对其进行获取,然后注入到属性中。

1.4 ApplicationContext

以 ApplicationContext 接口为核心发散出的几个类,主要是对前面 Resouce 、 BeanFactory、BeanDefinition 进行了功能的封装,解决 根据地址获取 IoC 容器并使用 的问题。

类名 说明
ApplicationContext 标记接口,继承了 BeanFactory。通常,要实现一个 IoC 容器时,需要先通过 ResourceLoader 获取一个 Resource,其中包括了容器的配置、Bean 的定义信息。接着,使用 BeanDefinitionReader 读取该 Resource 中的 BeanDefinition 信息。最后,把 BeanDefinition 保存在 BeanFactory 中,容器配置完毕可以使用。注意到 BeanFactory 只实现了 Bean 的 装配、获取,并未说明 Bean 的 来源 也就是 BeanDefinition 是如何 加载 的。该接口把 BeanFactory 和 BeanDefinitionReader 结合在了一起。
AbstractApplicationContext ApplicationContext 的抽象实现,内部包含一个 BeanFactory 类。主要方法有 getBean() 和 refresh() 方法。getBean() 直接调用了内置 BeanFactory 的 getBean() 方法,refresh() 则用于实现 BeanFactory 的刷新,也就是告诉 BeanFactory 该使用哪个资源(Resource)加载类定义(BeanDefinition)信息,该方法留给子类实现,用以实现 从不同来源的不同类型的资源加载类定义 的效果。
ClassPathXmlApplicationContext 从类路径加载资源的具体实现类。内部通过 XmlBeanDefinitionReader 解析 UrlResourceLoader 读取到的 Resource,获取 BeanDefinition 信息,然后将其保存到内置的 BeanFactory 中。

二、IOC容器的初始化

2.1 ApplicationContext 的构造和执行

ApplicationContext 的核心方法是 refresh() 方法,用于从资源文件加载类定义、扩展容器的功能。
refresh 的流程:

  1. loadBeanDefinitions(BeanFactory) :加载类定义,并注入到内置的 BeanFactory 中,这里的可扩展性在于,未对加载方法进行要求,也就是可以从不同来源的不同类型的资源进行加载。
  2. registerBeanPostProcessors(BeanFactory) :获取所有的 BeanPostProcessor,并注册到 BeanFactory 维护的 BeanPostProcessor 列表去。
  3. onRefresh :
    a. preInstantiateSingletons :以单例的方式,初始化所有 Bean。tiny-spring 只支持 singleton 模式。

2.2 BeanFactory 的构造与执行

BeanFactory 的核心方法是 getBean(String) 方法,用于从工厂中取出所需要的 Bean 。AbstractBeanFactory 规定了基本的构造和执行流程。

  1. doCreateBean :实例化 Bean。
    a. createInstance :生成一个新的实例。
    b. applyProperties :注入属性,包括依赖注入的过程。在依赖注入的过程中,如果 Bean 实现了 BeanFactoryAware 接口,则将容器的引用传入到 Bean 中去,这样,Bean 将获取对容器操作的权限,也就允许了 编写扩展 IoC 容器的功能的 Bean。
  2. initializeBean(bean) : 初始化 Bean。
    a. 从 BeanPostProcessor 列表中,依次取出 BeanPostProcessor 执行 bean = postProcessBeforeInitialization(bean,beanName) 。(为什么调用 BeanPostProceesor 中提供方法时,不是直接 post...(bean,beanName) 而是 bean = post...(bean,beanName) 呢?见分析1 。另外,BeanPostProcessor 列表的获取有问题,见分析2。)
    b. 初始化方法(tiny-spring 未实现对初始化方法的支持)。
    c. 从 BeanPostProcessor 列表中, 依次取出 BeanPostProcessor 执行其 bean = postProcessAfterInitialization(bean,beanName)。

2.3 tiny-spring 总体流程的分析

总体来说,tiny-spring 的 ApplicaitonContext 使用流程是这样的:

  1. ApplicationContext 完成了类定义的读取和加载,并注册到 BeanFactory 中去。
  2. ApplicationContext 从 BeanFactory 中寻找 BeanPostProcessor实例,注册到 BeanFactory
    维护的 BeanPostProcessor 列表中去,注意寻找的时候,如果BeanPostProcessor未创建实例,则创建。
  3. ApplicationContext 以单例的模式,通过主动调用 getBean 实例化、注入属性、执行beanPostProcessor;
  4. 使用方调用 getBean 时,委托给 BeanFactory,此时只是简单的返回每个 Bean 单例,因为所有的 Bean 实例在第三步都已经生成了。

三、AOP的植入和实现细节

3.1在 Bean 初始化过程中完成 AOP 的植入

解决 AOP 的植入问题,首先要解决 在 IoC 容器的何处植入 AOP 的问题,其次要解决 为哪些对象提供 AOP 的植入 的问题。
tiny-spring 中 AspectJAwareAdvisorAutoProxyCreator 类(以下简称 AutoProxyCreator)是实现 AOP 植入的关键类,它实现了两个接口:

  1. BeanPostProcessor :在 postProcessorAfterInitialization 方法中,使用动态代理的方式,返回一个对象的代理对象。解决了 在 IoC 容器的何处植入 AOP 的问题。
  2. BeanFactoryAware :这个接口提供了对 BeanFactory 的感知,这样,尽管它是容器中的一个 Bean,却可以获取容器的引用,进而获取容器中所有的切点对象,决定对哪些对象的哪些方法进行代理。解决了 为哪些对象提供 AOP 的植入 的问题。

3.2 AOP 中动态代理的实现步骤

动态代理的内容
首先,要知道动态代理的内容(拦截哪个对象、在哪个方法拦截、拦截具体内容),下面是几个关键的类:

类名 说明
PointcutAdvisor 切点通知器,用于提供 对哪个对象的哪个方法进行什么样的拦截 的具体内容。通过它可以获取一个切点对象 Pointcut 和一个通知器对象 Advisor。
Pointcut 切点对象可以获取一个 ClassFilter 对象和一个 MethodMatcher 对象。前者用于判断是否对某个对象进行拦截(用于 筛选要代理的目标对象),后者用于判断是否对某个方法进行拦截(用于 在代理对象中对不同的方法进行不同的操作)。
Advisor 通知器对象可以获取一个通知对象 Advice 。就是用于实现 具体的方法拦截,需要使用者编写,也就对应了 Spring 中的前置通知、后置通知、环切通知等。

动态代理的步骤
接着要知道动态代理的步骤:

  1. AutoProxyCreator(实现了 BeanPostProcessor 接口)在实例化所有的 Bean 前,最先被实例化。
  2. 其他普通 Bean 被实例化、初始化,在初始化的过程中,AutoProxyCreator 加载 BeanFactory 中所有的 PointcutAdvisor(这也保证了 PointcutAdvisor 的实例化顺序优于普通 Bean。),然后依次使用 PointcutAdvisor 内置的 ClassFilter,判断当前对象是不是要拦截的类。
    如果是,则生成一个 TargetSource(要拦截的对象和其类型),并取出 AutoProxyCreator 的 MethodMatcher(对哪些方法进行拦截)、Advice(拦截的具体操作),再,交给 AopProxy 去生成代理对象。
  3. AopProxy 生成一个 InvocationHandler,在它的 invoke 函数中,首先使用 MethodMatcher 判断是不是要拦截的方法,如果是则交给 Advice 来执行(Advice 由用户来编写,其中也要手动/自动调用原始对象的方法),如果不是,则直接交给 TargetSource 的原始对象来执行。

四、好问题

bean什么时候被创建?
获取的时候,懒加载

bean是怎么创建的?
反射创建实例对象,然后为对象补充属性,如果属性为引用类型,则先获取引用类型对应的bean,然后设置。

如何解决循环依赖的问题?
创建实例对象和设置引用类型的属性是分开的两个步骤,且创建完实例对象就已经放置到容器,不存在循环依赖的问题。
拓展:spring如何解决循环依赖

参考文章:
https://github.com/code4craft/tiny-spring/blob/master/changelog.md

你可能感兴趣的:(Spring源码系列一:临摹)