Spring 原理初探 —— IoC、AOP

前言

众所周知, 现在的 Spring 框架已经成为构建企业级 Java 应用事实上的标准了,众多的企业项目都构建在 Spring 项目及其子项目之上,特别是 Java Web 项目。

Spring 的两个核心概念是 IoC(控制反转)和 AOP(面向切面编程)。想了解 Spring 的工作原理,毫无疑问,首先要从这两个概念的 Spring 实现入手。但是 Spring 源码浩如烟海,里面掺杂了太多的实现细节,入门可谓极其困难。当我正苦于难以入门时,好友介绍了tiny-spring这个开源项目,这个项目用了不到千行的代码,就将 Spring 的 IoC、AOP 的核心流程实现完毕,真是居家旅行、吹逼面试之必备呀!

废话少说,我们开始吧!

目录结构

在 github 上 clone 下项目来之后,我们关注 src 文件夹,其余的是一些爱好者提的注释 PR,恰巧被作者 merge 了,不必理会。目录结构是这样的:

  1. aop包,顾名思义,实现了 Spring 的 AOP 功能,可以通过 bean 的自动 AOP 切入,文件稍多,暂时先不展开。
  2. bean.factory包,通过BeanFactoryAbstractBeanFactoryAutowireCapableBeanFactory三个类,实现了BeanFactory的核心功能,详情稍后讲解。
  3. bean.io包定义了资源加载相关的抽象概念,这里的资源包括 xml 配置文件等。
  4. bean.xml包中只包含一个类:XmlBeanDefinitionReader,主要负责在 xml 配置文件中读取 bean 定义。
  5. bean包其他类,定义了 BeanDefinition 等核心概念,详情后讲。
  6. context包定义了ApplicationContext的核心概念。
  7. BeanReference指的是引用类型的 Bean,而不是实体类。

IOC–浮沙筑台之根基

IOC(控制翻转)是一种编程范式,可以在一定程度上解决复杂系统对象耦合度太高的问题,并不是 Spring 的专利。IOC 最常见的方式是 DI(依赖注入),可以通过一个容器,将 Bean 维护起来,方便在其他地方直接使用,而不是重新 new。可以说,IOC 是 Spring 最基本的概念,没有 IOC 就没有 Spring。

为什么 DI 可以起到解耦的作用?
一个软件系统包含了大量的对象,每个对象之间都有依赖关系,在普通的软件编写过程中,对象的定义分布在各个文件之中,对象之间的依赖,只能通过类的构造器传参,方法传参的形式来完成。当工程变大之后,复杂的逻辑会让对象之间的依赖梳理变得异常困难。
在 Spring IOC 中,一般情况,我们可以在 XML 文件之中,统一的编写 bean 的定义,bean 与 bean 之间的依赖关系,从而增加逻辑的清晰度。而且,bean 的创建是由 Spring 来完成的,不需要编程人员关心,编程人员只需要将精力放到业务的逻辑上面,减轻了思维的负担。

tiny-spring里面,整个beanscontext包都是用来实现IOC的。

beans包实现的核心关注点是BeanFactoryBeanFactory也叫作 Bean 容器,顾名思义,是用来盛放、管理 bean 的。
context包实现的核心关注是ApplicationContextApplicationContext也是用来获取 Bean 的,但是它更高层,它的面向用户是 Spring 的使用者,而 BeanFactory 面向的用户更多是 Spring 开发者。BeanFactory 定义了 Bean 初始化的流程,ApplicationContext 定义了从 XML 读取,到 Bean 初始化,再到使用的过程。

Bean 在哪定义?

刚才有说到,Spring 通常通过 xml 文件,来统一的描述 bean,bean 与 bean 的依赖关系。所以说,bean 的定义表述,发生在 xml 配置文件之中。这个 XML 文件就是我们需要读取的资源文件。

因此,首要任务就是研究与读取 XML 资源文件相关的类。

bean.io中存放的是读取资源文件的抽象概念。其中包含了三个类或者接口:

  1. Resource接口,这个接口只有一个方法,InputStream getInputStream() throws IOException;。实现这个接口的类就是一个抽象的资源,可以获取这个资源的输入流,从而获取其中的内容。
  2. UrlResource类,这个类实现了Resource接口,通过构造器传入一个 url 地址,代表的是这个 url 所对应的文件。
  3. ResourceLoader类,只有一个方法,public Resource getResource(String location)。输入 url 的文件地址(并不是真正的 URL 格式地址),来获取 Resource。

通过分析上面三个类、接口,我们知道,这个包完成了一个任务:通过ResourceLoader这个类,获取某一个地址的Resource,从而获取这个文件的输入流。因为使用了 Resource 概念,可以使用网络文件或者本地文件。

Bean 如何定义?

  1. BeanDefinition是 Bean 定义的核心概念,BeanDefinition包含了:bean 的对象、bean 的类类型、bean 的名字,bean 的所有属性。这个类对 bean 的基本信息做好一个包装。
  2. BeanDefinitionReader接口,只有一个方法:void loadBeanDefinitions(String location) throws Exception;,实现这个接口的类,具有将某一个文件中的所有 bean 定义载入的功能。所以BeanDefinitionReader定义了,在哪载入 bean 定义,至于载入到哪里、如何载入,稍后看具体实现。
  3. AbstractBeanDefinitionReader抽象类,上面刚说了实现了BeanDefinitionReader接口的类,具有将某一个文件中描述的 bean 定义载入的功能,AbstractBeanDefinitionReader就实现了这样一个抽象功能。它的作用就是定义,载入到哪和如何载入的问题。在这个类里面,有两个属性:Map<String,BeanDefinition> registry;ResourceLoader resourceLoader;registry是一个注册表,他保存的就是所有的 Bean 定义,Map 结构,key 是 bean 的名字,value 就是 BeanDefinition。resourceLoader描述了如何载入。
  4. XmlBeanDefinitionReader这是beans.xml包里面的唯一一个方法,也是最重要的方法之一。它继承了AbstractBeanDefinitionReader,实现了所有方法,解决了 bean 定义中:在哪载入、如何载入、载入到哪的三个大问题。这个类面向用户的方法有两个,一个是loadBeanDefinitions,毫无疑问,这个是必须的。另一个是getRegistry用来获取 bean 注册表,得到所有 bean 的信息,registry 是 bean 们在内存中实际的家。但是这个getRegistry方法并不是面向用户的,而是面向 ApplicationContext 的。
  5. PropertyValuePropertyValue代表一种抽象概念,在 xml 中,bean 的属性包括属性名和属性对象,PropertyValue就是这么一个实体。
  6. BeanReference代表的是 Bean 的属性不是真实对象,而是另一个 bean 的引用。

Bean 的组装全过程

上面两部分是铺垫,而 BeanFactory 才是重点对象。beans.factory包中有三个类用来定义 BeanFactory 相关的概念。

  1. BeanFactory接口,只有一个方法:Object getBean(String name) throws Exception;,实现这个接口的类,就具有了得到一个 bean 的能力。
  2. AbstractBeanFactory类,较为复杂。详情后讲。
  3. AutowireCapableBeanFactory继承了AbstractBeanFactory,实现了 applyPropertyValues 方法,通过反射,将 bean 的所有属性,通过 set 方法注入进去。

AbstractBeanFactory有三大属性:

  • beanDefinitionMap,类似于 registry,但是他是 BeanFactory 里面私有的,代表的是这个 BeanFactory 里面暂时有哪些 bean 定义。
  • beanDefinitionNames代表里面,这个 BeanFactory 里面有哪些 bean(名字)。
  • beanPostProcessors,代理处理器,AOP 会用到,详情后讲。

AbstractBeanFactory 实现了几大功能:

  • getBean,这是主要功能,可以获取一个 Bean 对象。
  • registerBeanDefinition,面向 ApplicationContext,用来将 XML 配置文件里面的 bean 定义注册到 BeanFactory 里面。
  • preInstantiateSingletons,面向 ApplicationContext,用来在开始的时候,将所有的 bean 都初始化完毕,避免懒加载。
  • addBeanPostProcessor添加代理处理器。
  • getBeansForType,在 BeanFactory 里面,获取某一个类型的所有 bean。

经过上面的分析,我们可以知道 BeanFactory 完成了 Bean 初始化的整个流程。BeanFactory 的工作流程如下:

  1. getBean, 在 beanDefinitionMap 里面得到 bean,如果没有的话,先初始化。(为什么会没有,因为 ApplicationContext 读取 xml 文件时候,只是给 BeanDefinition 服了类类型,并没有赋值对象,这个对象还是需要 BeanFactory 通过反射生成的)。
  2. createBeanInstance,通过反射,根据 BeanDefinition 的类对象新建实体对象->将得到的 bean 对象赋值给 beandefinition,然后将 BeanDefinition 里面的属性都注入到 Bean 里面,这就完成了 doCreateBean。
  3. initializeBean 就是调用 BeanPostProcessor 的 postProcessBeforeInitilizztion 方法和 postProcessAfterIntilizatin 方法,获取新的 bean,这里会在 aop 中用到。

好了,到这 BeanFactory 就讲完了,下面是更重要的 ApplicationContext。

ApplicationContext-用户与 BeanFactory 之间的桥梁

beans.context包有三个类、接口,完成了 ApplicationContext 的基本功能。

  1. ApplicationContext 接口,没有任何方法,只是继承了 BeanFactory 接口,暗示 ApplicationContext 与 BeanFactory 都是获取 Bean 的地方。
  2. AbstractApplicationContext抽象类,首先,它的构造函数接收入参 BeanFactory,所以说 ApplicationContext 内部具有一个 BeanFactory。类似于一种装饰器模式,但不是装饰器模式,类似于代理模式,但也不是代理模式。
    fresh 方法分为三个步骤:
  3. loadBeanDefinitions,这个是一个模板方法,需要子类实现,它的作用就是从某一个地方读取 BeanDefinition,然后写入到 ApplicationContext 自己的 BeanFactory 里面,这就是 ApplicationContext 与 BeanFactory 之间的联系,也就是 ApplicationContext 还负责了读取定义。
  4. registerBeanPostProcessors,这个就是在 BeanFactory 里面找到 BeanPostProcessor,然后将他们放到 BeanFactory 的 beanPostProcessors 容器里面,方便 BeanFactory 初始化使用。
  5. onRefresh 初始化一遍所有的 bean。 3.ClassPathXmlApplicationContext实现了 loadBeanDefinitions 的方法,将 xml 文件和 BeanFactory 结合在一起。

总结-ApplicationContext 初始化流程

ApplicationContext 初始化流程

总结-ApplicationContext 获取 bean 流程

AOP–移花接木之魔法

上一节,讲完了 Spring IOC 的整个流程,也就是 bean 从定义获取,到得到 bean 之间的整个流程。本节,我们接触一下 Spring 另一个重要概念,AOP。AOP 用途十分广泛,其中 Spring 内部的声明式事务和拦截器都是利用了 AOP 的强大威力,才得以优雅的实现。
AOP 是什么呢,简单来说,它可以让编程人员在不修改对象代码的情况下,为这个对象添加额外的功能或者限制。
很熟悉吧,这就是代理模式!

Java 中存在两种代理模式:
一种叫静态代理,就是通过接口继承复用的方式来完成的, 代理类与被代理对象实现相同的接口,然后代理类里面会拥有一个被代理对象,代理类与被代理对象相同的方法,活调用被代理对象的方法,不过中间会加以限制,您翻开任何一本设计模式相关的书,翻到代理模式这一节,讲的就是它了。

另一种叫做动态代理,动态代理就是允许我们在程序运行过程中,为动态生成的对象,动态的生成代理。显然,这比静态代理灵活太多了。
Java 默认提供了动态代理的实现方式,但是有限制,它要求被代理对象必须实现某一个接口。为了突破这一限制,为普通类也可以提供代理,CGLib 这个库横空出世。

因为 AOP 涉及的知识较为复杂,所以我先将背景知识介绍一下。

  1. Java 动态代理,就是 Java 本身提供的代理实现,要求对象必须实现某一个接口。
  2. CGLib 库,为 Java 提供了,为普通类提供代理的功能。
  3. aopalliance,aop 联盟包,统一类 aop 编程的一些概念,这个包里没有具体的类实现,而是定义了几个重要的概念接口,具体的 aop 实现,要遵从这些接口编程,才能达到一定的通用性。
  4. aspectj 包,实现了,通过一种固定的编程语言,通过这种简单的编程语言,我们可以定位到被代理的类,自动完成代理。

在 aopallicance 里面,定义了几个核心概念:

  • Advice,增强,实现这个接口,说明这个类负责某一种形式的增强。
  • Joinpoint,连接点,表示切点与目标方法连接处的信息。
  • MethodInterceptor,继承了 Interceptor 接口,而 Interceptor 继承了 Advice 接口,是一种 Advice,但是有一个方法 invoke。这个方法需要一个参数 MethodInvocation。
  • MethodInvocation,表示的是连接点的信息以及连接点函数的调用。

结合上面的信息,我们发现,其实 MethodInterceptor 的 invoke 方法,调用的就是 MethodInvocation 的 proceed 方法,而这个 proceed 方法呢,应该调用的肯定是 Method.invoke 方法。所以,这是一种变相调用 method.invoke 的方式。为什么这样做呢,猜一猜的话,肯定是为了代码的复用,哈哈哈,这是废话。

在 Spring 中,还定义了几个核心概念:

  • Pointcut,切点,可以定位类以及方法。
  • Advisor,可以获取一个增强。
  • PointcutAdvisor,定义了哪些方法,具有什么类型的增强。
  • MethodMatcher,表示某一个方法是否符合条件。
  • ClassFilter,定义了某个类是否符合条件。
  • TargetSource,被代理的对象,包括对象本身,类类型、所有接口。
  • AdvisedSupport,代理相关的元数据,包括被代理的对象,增强等。
  • ProxyFactory,代理工厂,可以获得一个代理对象,同时具有 AdvisedSupport 的所有特性。
  • Cglib2AopProxy,使用 cglib 实现的动态代理类,继承了 AbstractAopProxy 抽象类,这个类的主要方法就是 getProxy,通过什么呢,通过 AdvisorSupport。
  • ReflectiveMethodInvocation,可以获取连接点的信息,代理的对象等。
  • JdkDynamicAopProxy,和 Cglib2AopProxy 类一个作用,通过 AdvisorSupport 来 getProxy,不过是使用 Java 自带的动态代理实现的。

其中,ProxyFactory 是获取一个代理对象的直接工厂,而这个代理对象,可以通过 Cglib2AopProxy 产生,也可以通过 JdkDynamicAopProxy 产生。

Spring AOP 之所以能够为动态生成的 Bean 提供代理,得益于 PostProcessor 接口。我们会议 IOC 初始化流程中,最后一部,就是得到 BeanFactory 之中所有继承了 PostProcessor 接口的 bean,调用它们的 postProcessBeforeInitilization、postProcessAfterInitilization 方法,来代理 bean,生成新的 bean。

基于这个突破口,我们只需要在 xml 配置文件中,放入 PostProcessor 对象,Spring 就会自动的用这写对象,来代理真正的对象。

在这里,我们的对象是 AspectJAwareAdvisorAutoProxyCreator。
在这个对象的方法中,逻辑是这样的,找到 xml 里面所有切面 bean,然后在这些 bean 里面,找到符合被代理类的切面 bean,找到切面 bean 之后,就可以获得增强,切点等,于是可有构造一个 AdvisorSupport,知道了 AdvisorSupport,我们就能够通过 proxyFactory 来获取代理了。

至于如何这个类切面是用来切入代理类的,这个就要交给 PointCut 来实现了,pointcut 有很多实现方式,这里我们用的是 aspectj。具体这个类我就不细讲了。

到目前位置,我自己已经将整个 AOP 的流程搞清楚了,下面通过流程图的形式展示出来:


Spring 原理初探 —— IoC、AOP
https://www.haoyizebo.com/posts/9529de0e/
作者
一博
发布于
2016年8月11日
许可协议