目录
- 写在前面
- 一、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 的流程:
- loadBeanDefinitions(BeanFactory) :加载类定义,并注入到内置的 BeanFactory 中,这里的可扩展性在于,未对加载方法进行要求,也就是可以从不同来源的不同类型的资源进行加载。
- registerBeanPostProcessors(BeanFactory) :获取所有的 BeanPostProcessor,并注册到 BeanFactory 维护的 BeanPostProcessor 列表去。
- onRefresh :
a. preInstantiateSingletons :以单例的方式,初始化所有 Bean。tiny-spring 只支持 singleton 模式。
2.2 BeanFactory 的构造与执行
BeanFactory 的核心方法是 getBean(String) 方法,用于从工厂中取出所需要的 Bean 。AbstractBeanFactory 规定了基本的构造和执行流程。
- doCreateBean :实例化 Bean。
a. createInstance :生成一个新的实例。
b. applyProperties :注入属性,包括依赖注入的过程。在依赖注入的过程中,如果 Bean 实现了 BeanFactoryAware 接口,则将容器的引用传入到 Bean 中去,这样,Bean 将获取对容器操作的权限,也就允许了 编写扩展 IoC 容器的功能的 Bean。 - 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 使用流程是这样的:
- ApplicationContext 完成了类定义的读取和加载,并注册到 BeanFactory 中去。
- ApplicationContext 从 BeanFactory 中寻找 BeanPostProcessor实例,注册到 BeanFactory
维护的 BeanPostProcessor 列表中去,注意寻找的时候,如果BeanPostProcessor未创建实例,则创建。 - ApplicationContext 以单例的模式,通过主动调用 getBean 实例化、注入属性、执行beanPostProcessor;
- 使用方调用 getBean 时,委托给 BeanFactory,此时只是简单的返回每个 Bean 单例,因为所有的 Bean 实例在第三步都已经生成了。
三、AOP的植入和实现细节
3.1在 Bean 初始化过程中完成 AOP 的植入
解决 AOP 的植入问题,首先要解决 在 IoC 容器的何处植入 AOP
的问题,其次要解决 为哪些对象提供 AOP 的植入
的问题。
tiny-spring 中 AspectJAwareAdvisorAutoProxyCreator 类(以下简称 AutoProxyCreator)是实现 AOP 植入的关键类,它实现了两个接口:
- BeanPostProcessor :在 postProcessorAfterInitialization 方法中,使用动态代理的方式,返回一个对象的代理对象。解决了 在 IoC 容器的何处植入 AOP 的问题。
- BeanFactoryAware :这个接口提供了对 BeanFactory 的感知,这样,尽管它是容器中的一个 Bean,却可以获取容器的引用,进而获取容器中所有的切点对象,决定对哪些对象的哪些方法进行代理。解决了 为哪些对象提供 AOP 的植入 的问题。
3.2 AOP 中动态代理的实现步骤
动态代理的内容
首先,要知道动态代理的内容(拦截哪个对象、在哪个方法拦截、拦截具体内容),下面是几个关键的类:
类名 | 说明 |
---|---|
PointcutAdvisor | 切点通知器,用于提供 对哪个对象的哪个方法进行什么样的拦截 的具体内容。通过它可以获取一个切点对象 Pointcut 和一个通知器对象 Advisor。 |
Pointcut | 切点对象可以获取一个 ClassFilter 对象和一个 MethodMatcher 对象。前者用于判断是否对某个对象进行拦截(用于 筛选要代理的目标对象),后者用于判断是否对某个方法进行拦截(用于 在代理对象中对不同的方法进行不同的操作)。 |
Advisor | 通知器对象可以获取一个通知对象 Advice 。就是用于实现 具体的方法拦截,需要使用者编写,也就对应了 Spring 中的前置通知、后置通知、环切通知等。 |
动态代理的步骤
接着要知道动态代理的步骤:
- AutoProxyCreator(实现了 BeanPostProcessor 接口)在实例化所有的 Bean 前,最先被实例化。
- 其他普通 Bean 被实例化、初始化,在初始化的过程中,AutoProxyCreator 加载 BeanFactory 中所有的 PointcutAdvisor(这也保证了 PointcutAdvisor 的实例化顺序优于普通 Bean。),然后依次使用 PointcutAdvisor 内置的 ClassFilter,判断当前对象是不是要拦截的类。
如果是,则生成一个 TargetSource(要拦截的对象和其类型),并取出 AutoProxyCreator 的 MethodMatcher(对哪些方法进行拦截)、Advice(拦截的具体操作),再,交给 AopProxy 去生成代理对象。 - AopProxy 生成一个 InvocationHandler,在它的 invoke 函数中,首先使用 MethodMatcher 判断是不是要拦截的方法,如果是则交给 Advice 来执行(Advice 由用户来编写,其中也要手动/自动调用原始对象的方法),如果不是,则直接交给 TargetSource 的原始对象来执行。
四、好问题
bean什么时候被创建?
获取的时候,懒加载
bean是怎么创建的?
反射创建实例对象,然后为对象补充属性,如果属性为引用类型,则先获取引用类型对应的bean,然后设置。
如何解决循环依赖的问题?
创建实例对象和设置引用类型的属性是分开的两个步骤,且创建完实例对象就已经放置到容器,不存在循环依赖的问题。
拓展:spring如何解决循环依赖
参考文章:
https://github.com/code4craft/tiny-spring/blob/master/changelog.md