《Spring 技术内幕 第 2 版》学习笔记

波波波先森关注

22018.08.19 18:53:46 字数 23,117 阅读 9,198

    本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那就点个小心心,文末赞赏一杯豆奶吧,嘻嘻。 让我们共同成长吧……


第 1 章  Spring 的设计理念和整体架构

    本章内容

        Spring 各个子项目、Spring 设计目标、Spring 整体架构、Spring 应用场景

1.1 Spring 各个子项目

        Spring Framework(Core) :包含一系列 IoC 容器的设计,提供依赖反转模式的实现;集成了 AOP;包含其他基本模块,例如 MVC、JDBC、事务处理模块的实现。

        Spring Web Flow:工作流引擎。  Spring BlazeDs Integration:Spring 提供的与 Flex 通讯模块。 Spring Security:Spring 认证和安全框架。

        Spring Security OAuth:OAuth 在 Spring 的实现上提供支持。Spring Dynamic Modules:Spring 对 OSGi 平台的支持。

        Spring Batch:提供构建批处理应用和自动化操作的框架。Spring AMQP:Spring 更好的使用基于 AMQP(高级消息队列协议)的消息服务而开发。

        Spring .NET:.NET 环境中的 Spring。Spring Android:在 Andriod 环境中基于 Java REST 客户端。

        Spring Data:为 Spring 使用非关系型数据库提供帮助,比如使用分布式,k-v 数据库等。

        Spring Integration、Spring Mobile、Spring Social 等模块

1.2 Spring 设计目标

       ** Spring 设计目标是 **:Spring 为开发者提供一个一站式轻量级应用开发平台;

       ** Spring 设计理念是 **:在 JavaEE 开发中,支持 POJO 和 JavaBean 开发方式,使应用面向接口开发,充分支持 OO 设计方法;Spring 通过 IoC 容器实现对象耦合关系的管理,并实现依赖反转,将对象之间的依赖关系交个 IoC 容器,实现解耦;

       ** Spring 体系的核心 **:IoC 容器和 AOP 模块。通过 IoC 容器管理 POJO 对象以及他们之间的相互耦合关系;通过 AOP 以动态非侵入的方式增强服务。

1.3 Spring 整体架构

null

Spring 架构图

        将 Spring 划分为:Spring IoC、Spring AOP、Spring MVC、Spring JDBC/ORM、Spring 事务处理、Spring 远端调用、Spring 应用。

       ** Spring IoC**:包含最基本的 IoC 容器 BeanFactory 接口的设计与实现。例如,XmlBeanFactory 是 BeanFactory 的实现。还有 IoC 容器的高级形式 ApplicationContext,例如:FileSystemXMLApplicationContext、ClassPathXMLApplicationContext 等实现。

        Spring AOP:在 JDK 动态代理和 CGLIB 基础上实现了 AOP,Spring 还集成了 AspectJ 作为 AOP 的一个特定实现。

        Spring MVC:该模块是以 DispatcherSerlet 为核心,实现 MVC 模式。

        Spring JDBC/ORM:Spring 对 JDBC 进行封装,提供了 JdbcTemplate 作为模板类,封装了基本的数据库操作方法(查询、更新);Spring JDBC 还提供了 RDBMS 的操作对象,使得这些操作可以以面向对象的方法来使用 JDBC。

        Spring 事务处理:该模块是通过 Spring AOP 实现的自身功能增强,实现了声明式事务处理功能。

        Spring 远端调用:通过 Spring,封装从 Spring 应用到 Spring 应用之间端到端调用。

        Spring 应用:严格意义上不属于 Spring 范围。该部分来自于 Spring 的广泛的子项目。

1.4  Spring 应用场景

    应用场景:SSH、SSM

    Spring 价值:Spring 是非侵入式的框架,目标是使应用程序代码对框架依赖最小化;Spring 提供一个一致的编程模型,使应用直接使用 POJO 开发,与运行环境隔离开来;Spring 推动应用设计风格向面向对象和面向接口开发转变,提高了代码的重用性和可测试;

1.5  小结



第一部分  Spring 核心实现篇

        本部分对 spring 的核心 IoC 容器和 AOP 特性的实现原理进行讲解。Spring 的核心是 Spring 系统中其他组件模块和应用开发的基础。

第 2 章   Spring FrameWork 的核心:IoC 容器的实现

    本章内容

        Spring IoC 容器概述、IoC 容器系列的设计与实现:BeanFactory 和 ApplicationContext、IoC 容器初始化过程、IoC 容器的依赖注入、容器其他相关特性的设计与实现

2.1  Spring IoC 容器概述

    2.1.1  IoC 容器和依赖反转模式

        在面向对象中,对象封装了数据和对数据的处理,对象的依赖关系体现在对数据和方法的依赖上。这些依赖关系可以通过把对象的依赖注入交给框架或 IoC 容器来完成,这样做可以解耦代码也可以提高代码的可测试性。依赖控制反转的实现方式很多,在 Spring 中 IoC 容器就是这个模式的载体(或实现),可它以在对象生成和初始化时直接将数据注入到对象中,也可以通过将对象引用注入到对象数据域中的方式来注入对方法调用的依赖。这种依赖注入是可以递归的,对象被逐层注入。控制反转是关于一个对象如何获取它所依赖对象的引用,在这里是责任的反转。

        IoC 容器进行对象关系的管理,并由 IoC 容器完成对象的注入。

    2.1.2 Spring IoC 的应用场景

        在 Spring 中,SpringIoC 提供了基本的 JavaBean 容器,通过 IoC 模式管理以来关系,并通过依赖注入和 AOP 切面增强了为 JavaBean 这样的 POJO 对象赋予事务管理、生命周期管理等基本功能。具体的注入方式有:接口注入、构造器注入、setter 注入。

2.2   IoC 容器系列的设计与实现:BeanFactory 和 ApplicationContext

        Spring IoC 容器中有两个主要的容器系列:(1)BeanFactory:实现了容器的基本功能;(2)ApplicationContext:在 BeanFactory 的基础上增添了功能,是容器的高级形式。

    2.2.1  Spring 的 IoC 容器系列

        Spring 的 IoC 容器系列概况:

null

Spring 的 IoC 容器系列概况

        BeanFactory 和 ApplicationContext 都是 IoC 容器的具体表现形式。BeanFactory 体现了 Spring 为用户使用 IoC 容器所设计的最基本的功能规范。在 Spring 提供的基本 IoC 容器接口定义和实现基础上,Spring 通过 BeanDefinition 来管理基于 Spring 的应用中的各种对象以及他们之间的相互依赖关系,BeanDefinition 抽象了对 Bean 的定义,是容器起作用的主要数据类型。

    2.2.2  Spring 的 IoC 容器的设计

null

IoC 容器的接口设计图

        o 从 BeanFactory 到 HierarchicalBeanFactory,再到 ConfigurableBeanFactory,这是一条主要的 Beanfactory 设计路径,该路径定义了基本的 IoC 容器的规范。

        o 第二条接口设计主线是以 ApplicationContext 为主的接口设计。

        o 这里主要涉及的是接口关心,具体的 IoC 实现都是在这个接口体系下实现的,例如 DefaultListableBeanFactory, 是一个简单的 IoC 容器的实现。像其他的 IoC 容器,例如 XmlBeanFactory, 都是在 DefaultListableBeanFactory 的基础上扩展的,同样 ApplicationContext 也是如此。         

        1、BeanFactory 的应用场景

        BeanFactory 提供了最基本的 IoC 功能,这些可以在 BeanFactory 接口中看到。BeanFactory 的具体实现类有:DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext 都是 BeanFactory 基础上添加了相应的功能。

        用户使用容器时,可以使用转义符“&”来获得 FactoryBean 本身, 用来区分获取 FactoryBean 产生的对象和获取 FactoryBean 本身。

null

BeanFactory 接口

      2、BeanFactory 容器的设计原理

        以 XmlBeanFactory 的实现来讲解 IoC 容器的设计原理。XmlBeanFactory 设计的类继承关系:

null

XmlBeanFactory 设计的类继承关系

        DefaultListableBeanFactory 包含了基本 IoC 容器所具有的的重要功能,作为 Spring 中的默认的功能完整的 IoC 容器来使用。XmlBeanFactory 继承 DefaultListableBeanFactory,除了具备基本的功能之外,还具备读取以 XML 文件定义的 BeanDefinition 的能力。XML 文件是由 XmlBeanFactory 初始化的 XmlBeanDefinitionReader 来处理以 XML 方式定义的 BeanDefinition。构造 XmlBeanFactory 这个 IoC 容器需要 Resource 类给出 BeanDefinition 的信息来源。Resource 是 Spring 用来封装 I/O 操作的类,例如 ClassPathResource(“*.xml”) 等。

       XmlBeanFactory 实现:

null

  XmlBeanFactory 实现

        参考 XmlBeanFactory 的实现,以编程方式使用 IoC 容器:

null

编程式使用 IoC 容器

       步骤:

            1) 创建 IoC 配置文件的抽象资源,该象资源包含了 BeanDefinition 的定义信息。

            2) 创建一个 BeanFactory,这里使用 DefaultListableBeanFactory。

            3) 创建一个载入 BeanDefinition 的读入器,这里使用 XmlBeanDefinitionReader 来载入 XML 文件形式的 BeanDefinition,通过一个回调配置给 BeanFactory。

            4) 从定义好的资源位置读入配置信息,具体的解析过程由 XmlBeanDefinitionReader 来完成。完成整个载入和注册 Bean 定义之后,需要的 IoC 容器就建立起来了。这个时候就可以直接使用 IoC 容器了。

     **   3、ApplicationContext 的应用场景 **

        ApplicationContext 的接口关系:

null

 ApplicationContext 的接口关系

        这些功能为 ApplicationContext 提供了一些 BeanFactory 所不具备的新特性:

            1)支持不同的信息源。ApplicationContext 扩展了 MessageResource 接口,这些信息源的功能可以支持国际化的实现。

            2)访问资源。这一特性体验在 Resource 与 ResourceLoader 上,这样一来,我们可以从不同的地方得到 Bean 定义资源。

            3)支持应用事件。继承了接口 ApplicationEventPublisher,从而在上下文中引入了事件机制。这些事件和 Bean 的生命周期的结合为 Bean 的管理提供了便利。

            4)在 ApplicationContext 中提供的附加服务。这些服务使得基本的 IoC 容器的基本功能更加丰富。

      **  4、ApplicationContext 容器的设计原理 **

        以 FileSystemXmlApplicationContext 来说明 ApplicationContext 容器的设计原理。

        ApplicationContext 主要功能在 FileSystemXmlApplicationContext 的基类 AbstractXmlApplicationContext 中已经实现了,在 FileSystemXmlApplicationContext 中,作为一个具体的应用上下文,只需要实现和它自身设计相关的 2 个功能。

        一个功能是,如果应用直接使用 FileSystemXmlApplicationContext ,对于实例化这个应用上下文的支持,同时启动 IoC 容器的 refresh() 过程。

null

FileSystemXmlApplicationContext 

        另外一个功能是与 FileSystemXmlApplicationContext 设计具体相关的功能,这部分与怎样从文件系统中加载 XML 的 bean 定义资源有关。

null

getResourceByPath

    2.3 IoC 容器初始化过程

        IoC 容器的初始化过程是通过 refresh() 方法来启动的,该方法标识着 IoC 容器正式启动。启动过程包括:BeanDefinition 的 Resource 定位、载入和注册三个基本过程。Spring 把这三个过程分开,并使用不同的模块来完成,从而方便自己定义 IoC 容器的初始化过程。

       ** 1、BeanDefinition 的 Resource 的定位 **。BeanDefinition 的资源定位,它由 ResourceLoader 通过统一的 Resource 接口来完成。对于 BeanDefinition 的存在形式,可以是文件系统中的通过 FileSystemResource 来进行抽象;类路径中定义的 Bean 信息可以通过 ClassPathResource 来进行抽象。这个定位过程类似于容器寻找数据的过程。

       2、BeanDefinition 的载入。载入过程是把用户定义好的 Bean 定义成 IoC 容器内部的数据结构 BeanDefinition。BeanDefinition 实际上就是 POJO 对象在 IoC 容器中的抽象,通过这个 BeanDefinition 定义的数据结构,使 IoC 容器能够方便地管理 Bean。

        3、BeanDefinition 的注册。注册过程是通过 BeanDefinitionRegistry 接口的实现来完成的。注册过程把载入过程中解析得到 BeanDefinition 向 IoC 容器进行注册。在 IoC 容器内部将 BeanDefinition 注入到一个HashMap中去,IoC 容器就是通过这个 HashMap 来持有这些 Bean 数据的。

        _以上 IoC 容器的初始化过程,并不包含 Bean 依赖注入的实现。_ 在 Spring IoC 的设计中,Bean 定义的载入和依赖注入是两个独立的过程。依赖注入一般发生在第一个通过 getBean()向容器中获取 Bean 的时候,也并不全是这样,例如某个 Bean 设置了 lazyinit 属性,那么 Bean 的依赖注入在其初始化的过程中就已经完成了,而不需要等到整个容器初始化完成之后,第一次使用 getBean() 才会触发。

     2.3.1  BeanDefinition 的 Resource 定位

        以编程的方式使用 DefaultListableBeanFactory 时,首先定义一个 Resource 来定位容器使用的 BeanDefinition。使用 ClassPathResource,意味着 Spring 会在类路径中去寻找以文件形式存在的 BeanDefinition 信息 。ClassPathResource res = new ClassPathResource("beans.xml"); 

        这里定义的 Resource 并不能由 DefaultListableBeanFactory 直接使用,Spring 通过 BeanDefinitionReader 来对这些信息进行处理。回到常用的 ApplicationContext 上,例如:FileSystemXmlApplicationContext、ClassPathXmlApplicationContext、XmlWebApplicationContext 等。简单地从这些类的名字上分析,可以清楚地看到它们可以提供哪些不同的 Resource 读入功能。

        以 FileSystemXmlApplicationContext 为例,来分析 ApplicationContext 的实现是如何完成 Resource 定位的,下面是这个类对应的继承体系。

null

        下面从源码角度,更为详细的继承体系:

null

源码角度 FileSystemXmlApplicationContext 的继承关系

        从上图中可以看出 FileSystemXmlApplicationContext 已经通过继承 AbstractApplicationContext 具备了 ResourceLoader 的功能,因为 AbstractApplicationContext 继承自 DefaultResourceLoader。下面是其源代码:

null

FileSystemXmlApplicationContext 源代码

        对 BeanDefinition 资源定位的过程,是由 refresh()方法触发的,refresh() 方法的调用是在 FileSystemXmlApplicationContext 的构造函数中启动的。下图可清楚的看出整个 BeanDefinition 资源定位的过程:

          资源定位时序图如下:

null

getResourceByPath 的调用过程(资源定位时序图)

        在 BeanDefinition 定位完成基础上,就可通过返回的 Resource 对象进行 BeanDefinition 载入。在完成定位过程后,为 BeanDefinition 的载入创造了 I/O 操作的条件,但具体的数据还没有开始读入,读入将在 BeanDefinition 的载入和解析中完成。

    2.3.2 BeanDefinition 的载入和解析

        完成 BeanDefinition 的 Resource 定位的后,便可进行 BeanDefinition 信息的载入过程。该过程相当于把定义的 BeanDefinition 在 IoC 容器中转化成一个 Spring 内部表示的数据结构的过程。IoC 容器对 Bean 的管理和依赖注入功能的实现,是通过对其持有的 BeanDefinition 进行各种相关操作来完成的。这些 BeanDefinition 数据在 IoC 容器中通过一个 HashMap 来保持和维护。

        从 DefaultListableBeanFactory 的设计入手来看 IoC 容器是怎样完成 BeanDefinition 载入的。先回到 IoC 容器的初始化入口 refresh()方法。该方法最初是在 FileSystemXmlApplicationContext 的构造函数中被调用的,它的调用标志着容器初始化的开始,这些初始化对象就 BeanDefinition 数据,初始化入口函数:

null

启动 BeanDefinition 的载入

       ** IoC 容器的 refresh 过程代码:**

null

IoC 容器的 refresh 过程代码

        FileSystemXmlApplicationContext 是怎么完成信息的读入的呢?读入器的配置,可以在 FileSystemXmlApplicationContext 的基类 AbstractRefreshableApplicationContext 中的 refreshBeanFactory()方法的实现中查看。refreshBeanFactory() 方法被 FileSystemXmlApplicationContext 构造函数中的 refresh()方法所调用。在该方法中,通过 createBeanFactory() 创建一个 DefaultListableBeanFactory 的 IoC 容器供 ApplicationContext 使用。同时,还启动了 loadBeanDefinitions() 来载入 BeanDefinition。

        AbstractRefreshableApplicationContext 中的 refreshBeanFactory() :

null

AbstractRefreshableApplicationContext 的 refreshBeanFactory  ()

      **  AbstractXmlApplicationContext 的 loadBeanDefinitions():**

null

       ** XmlBeanDefinitionReader 载入 BeanDefinition(), 代码清单如下:**

null

     **   AbstractBeanDefinitionReader 载入 BeanDefinition:**

null

        调用的 loadBeanDefinitions(Resource res) 方法在 AbstractBeanDefinitionReader 类里是没有实现的,具体的实现在 XmlBeanDefinitionReader 中。在读取器中,需要得到封装了对 XML 文件的 I/O 操作的代表 XML 文件的 Resource,读取器可以在打开 I/O 流后得到 XML 的文件对象。有了这个文件对象以后,就可按照 Spring 的 Bean 定义规则来对这个 XML 的文档树进行解析了,该解析是交给 BeanDefinitionParserDelegate 来完成的,  对 BeanDefinition 的载入实现:

null

null

null

    **  BeanDefinition 载入时序图:**

null

            BeanDefinition 载入时序图

       完成资源的载入后,需要将载入的 BeanDefinition 进行解析并转化为 IoC 容器内部数据结构, 这个过程在 **registerBeanDefinitions()** 成,具体是由 BeanDefinitionsDocumentReader 来完成。

null

        _BeanDefinition 的载入分成两部分:_ 首先,通过调用 XML 的解析器得到 document 对象。然后,按照 Spring 的 Bean 规则进行解析。按照 Spring 的 Bean 规则进行解析的过程是在默认设置好的 DefaultBeanDefinitionDocumentReader 中实现的。DefaultBeanDefinitionDocumentReader 的创建是在后面的方法中完成的,然后再完成 BeanDefinition 的处理,处理的结果由 BeanDefinitionHolder 对象持有。这个 BeanDefinitionHolder 除了持有 BeanDefinition 对象外,还持有其他与 BeanDefinition 的使用相关的信息,比如 Bean 的名字、别名集合等。这个 BeanDefinitionHolder 的生成是通过对 Document 文档树的内容进行解析来完成的,可看到该解析过程是由 BeanDefinitionParserDelegate 实现的(processBeanDefinition 方法中实现)的,同时这个解析是与 Spring 对 BeanDefinition 的配置规则紧密相关的。

        创建 BeanDefinitionDocumentReader:

null

   ** 具体的 Spring BeanDefinition 的解析是在 BeanDefinitionParserDelegate 中完成的:**

null

null

        上面介绍了对 Bean 元素进行解析的过程,也就是 BeanDefinition 依据 XML 的定义被创建的过程。这个 BeanDefinition 可以看成是对定义的抽象。这个数据对象中封装的数据大多都是与定义相关的,也有很多就是我们在定义 Bean 时看到的那些 Spring 标记,比如常见的 init-method、destroy-method、factory-method 等。

        beanClass、description、lazyInit 这些属性都是在配置 bean 时经常碰到的,都集中在 BeanDefinition。通过解析以后,便可对 BeanDefinition 元素进行处理,在这个过程中可以看到对 Bean 定义的相关处理,比如对元素 attribute 值的处理,对元素属性值的处理,对构造函数设置的处理,等等。对 BeanDefinition 定义元素的处理,代码如下:

null

null

        上面是具体生成 BeanDefinition 的地方。在这里,我们举一个对 property 进行解析的例子来完成对整个 BeanDefinition 载入过程的分析,还是在类 BeanDefinitionParserDelegate 的代码中,一层一层地对 BeanDefinition 中的定义进行解析,比如从属性元素集合到具体的每一个属性元素,然后才是对具体的属性值的处理。根据解析结果,对这些属性值的处理会被封装成 PropertyValue 对象并设置到 BeanDefinition 对象中去,对 BeanDefinition 中 Property 元素集合的处理。

    2.3.3   BeanDefinition 在 IoC 容器中的注册

        BeanDefinition 在 IoC 容器中载入和解析的过程完成以后,用户定义的 BeanDefinition 信息已经在 IoC 容器内建立起了自己的数据结构以及相应的数据表示,但此时这些数据还不能供 IoC 容器直接使用,需要在 IoC 容器中对这些 BeanDefinition 数据进行注册。在 DefaultListableBeanFactory 中,是通过一个HashMap来持有载入的 BeanDefinition 的,这个 HashMap 的定义在 DefaultListableBeanFactory 中可以看到,如下所示。

               private final Map beanDefinitionMap = new  ConcurrentHashMap();

       将解析得到的 BeanDefinition 向 IoC 容器中的 beanDefinitionMap 注册的过程是在载入 BeanDefinition 完成后进行的,注册的调用过程如图所示:

        BeanDefinition 注册时序图:

null

注册的调用过程

        在 DefaultListableBeanFactory 中实现了BeanDefinitionRegistry的接口,该接口的实现完成 BeanDefinition 向容器的注册。**注册过程就是把解析得到的 BeanDefinition 设置到 hashMap 中去。** 需要注意的是,如果遇到同名的 BeanDefinition,进行处理的时候需要依据 allowBeanDefinitionOverriding 的配置来完成。

null

null

BeanDefinition 的注册

        完成了 BeanDefinition 的注册,就完成了 IoC 容器的初始化过程。此时,在使用的 IoC 容器 DefaultListableBeanFactory 中已经建立了整个 Bean 的配置信息,而且这些 BeanDefinition 已经可以被容器使用了,它们都在 beanDefinitionMap 里被检索和使用。容器的作用就是对这些信息进行处理和维护。

2.4  IoC 容器的依赖注入

        当前 IoC 容器已经载入了用户定义的 Bean 信息,用户第一次向 IoC 容器索要 Bean 时,即调用 getBean()的接口定义,触发依赖注入。下面从 DefaultListableBeanFactory 的基类 AbstractBeanFactory 入手去看看 getBean 的实现, getBean 触发的依赖注入,代码如下:

null

null

null

null

        依赖注入的大致过程:

null

依赖注入的大致过程

        getBean 是依赖注入的起点,之后会调用 createBean,下面通过 createBean 代码来了解这个实现过程。在这个过程中,Bean 对象会依据 BeanDefinition 定义的要求生成。在 AbstractAutowireCapableBeanFactory 中实现了这个 createBean,createBean 不但生成了需要的 Bean,还对 Bean 初始化进行了处理,比如实现了在 BeanDefinition 中的 init-method 属性定义,Bean 后置处理器等。 AbstractAutowireCapableBeanFactory 中的 createBean 具体的过程如代码如下:

null

null

null

null

        与依赖注入关系特别密切的方法有createBeanInstance() 和populateBean()。在 **createBeanInstance()** 中生成了 Bean 所包含的 Java 对象,这个对象的生成有很多种不同的方式,可以通过工厂方法生成,也可以通过容器的 autowire 特性生成,这些生成方式都是由相关的 BeanDefinition 来指定的。 Bean 包含的 Java 对象的生成, 代码如下:

null

null

        这里用 CGLIB 对 Bean 进行实例化,IoC 容器中,要了解怎样使用 cglib 来生成 Bean 对象,需要看一下 SimpleInstantiationStrategy 类。这个 Strategy 是 Spring 用来生成 Bean 对象的默认类,它提供了两种实例化 Java 对象的方法,一种是通过 BeanUtils,它使用了 JDK 的反射功能,一种是通过前面提到的 CGLIB 来生成, 使用 SimpleInstantiationStrategy 生成 Java 对象,代码如下:

null

使用 SimpleInstantiationStrategy 生成 Java 对象

      **  以上分析了实例化 Bean 对象的整个过程 **。在实例化 Bean 对象生成的基础上,怎样把这些 Bean 对象的依赖关系设置好,完成整个依赖注入过程。这个过程涉及对各种 Bean 对象的属性的处理过程(即依赖关系处理的过程),这些依赖关系处理的依据就是已经解析得到的 BeanDefinition。要详细了解这个过程,需要回到前面的 **populateBean()** 方法,这个方法在 AbstractAutowireCapableBeanFactory 中的 populateBean()实现如代码如下:

null

null

null

null

null

        这里通过使用 BeanDefinitionResolver 来对 BeanDefinition 进行解析然后注入到 property 中。下面到 BeanDefinitionValueResolver 中去看一下解析过程的实现,以对 Bean reference 进行解析为例具体的对 Bean reference 进行解析的过程如代码清单 2-28 所示:

null

null

BeanDefinitionValueResolver

        这两种属性的注入都调用了resolveValueIfNecessary这个方法包含了所有对注入类型的处理。下面看一下 resolveValueIfNecessary 的实现,如代码清单如下:

null

null

null

null

        在完成这个解析过程后,已经为依赖注入准备好了条件,这是真正把 Bean 对象设置到它所依赖的另一个 Bean 的属性中去的地方,其中处理的属性是各种各样的。依赖注入的发生是在 BeanWrapper 的 setPropertyValues 中实现的,具体的完成却是在 BeanWrapper 的子类 BeanWrapperImpl 中实现的,如代码清单如下:

null

null

null

null

null

null

null

        这样就完成了对各种 Bean 属性的依赖注入过程。在 Bean 的创建和对象依赖注入的过程中,需要依据 BeanDefinition 中的信息来递归地完成依赖注入。从上面的几个递归过程中可以看到,这些递归都是以 getBean 为入口的。一个递归是在上下文体系中查找需要的 Bean 和创建 Bean 的递归调用;另一个递归是在依赖注入时,通过递归调用容器的 getBean 方法,得到当前 Bean 的依赖 Bean,同时也触发对依赖 Bean 的创建和注入。在对 Bean 的属性进行依赖注入时,解析的过程也是一个递归的过程。这样,根据依赖关系,一层一层地完成 Bean 的创建和注入,直到最后完成当前 Bean 的创建。有了这个顶层 Bean 的创建和对它的属性依赖注入的完成,意味着和当前 Bean 相关的整个依赖链的注入也完成了。

        在 Bean 创建和依赖注入完成以后,在 IoC 容器中建立起一系列依靠依赖关系联系起来的 Bean,这个 Bean 已经不是简单的 Java 对象了。该 Bean 系列以及 Bean 之间的依赖关系建立完成以后,通过 IoC 容器的相关接口方法,就可以非常方便地供上层应用使用了。

2.5  容器其他相关特性的设计与实现

    2.5.1   ApplicationContext 和 Bean 的初始化及销毁

        对于 BeanFactory,特别是 ApplicationContext,容器自身也有一个初始化和销毁关闭的过程。下面详细看看在这两个过程中,应用上下文完成了什么,可以让我们更多地理解应用上下文的工作,容器初始化和关闭过程可以简要地通过下图表现:

null

IoC 容器初始化和关闭过程

        从图中可以看到,对ApplicationContext 启动的过程是在 AbstractApplicationContext 中实现的。在使用应用上下文时需要做一些准备工作,这些准备工作在 prepareBeanFactory()方法中实现。在这个方法中,为容器配置了 ClassLoader、PropertyEditor 和 BeanPostProcessor 等,从而为容器的启动做好了必要的准备工作。

null

        同样,在容器要关闭时,也需要完成一系列的工作,这些工作在 doClose( ) 方法中完成。在这个方法中,先发出容器关闭的信号,然后将 Bean 逐个关闭,最后关闭容器自身。

null

      **  以上是容器的初始化和销毁的设计与实现。在这个过程中需要区分 Bean 的初始化和销毁过程。**Spring IoC 容器提供了相关的功能,可以让应用定制 Bean 的初始化和销毁过程。容器的实现是通过 IoC 管理 Bean 的生命周期来实现的。Spring IoC 容器在对 Bean 的生命周期进行管理时提供了 Bean 生命周期各个时间点的回调。在分析 Bean 初始化和销毁过程的设计之前,简要介绍一下 IoC 容器中的 Bean 生命周期:

      Bean 实例的创建 -> 为 Bean 实例设置属性 -> 调用 Bean 的初始化方法 -> 通过 IoC 容器使用 Bean -> 容器关闭时调用 Bean 的销毁方法

        Bean 的初始化方法调用是在以下的 initializeBean 方法中实现的:

null

        在调用 Bean 的初始化方法之前,会调用一系列的 aware 接口实现,把相关的 BeanName、BeanClassLoader,以及 BeanFactoy 注入到 Bean 中去。接着会看到对 invokeInitMethods 的调用,这时还会看到启动 afterPropertiesSet 的过程,当然,这需要 Bean 实现 InitializingBean 的接口,对应的初始化处理可以在 InitializingBean 接口的 afterPropertiesSet 方法中实现,这里同样是对 Bean 的一个回调。

null

        最后,还会看到判断 Bean 是否配置有 initMethod,如果有,那么通过 invokeCustomInitMethod 方法来直接调用,最终完成 Bean 的初始化。

null

        在这个对 initMethod 的调用中,可以看到首先需要得到 Bean 定义的 initMethod,然后通过 JDK 的反射机制得到 Method 对象,直接调用在 Bean 定义中声明的初始化方法。

        与 Bean 初始化类似,当容器关闭时,可以看到对 Bean 销毁方法的调用。Bean 销毁过程是这样的:

null

        其中的 destroy 方法,对 Bean 进行销毁处理。最终在 DisposableBeanAdapter 类中可以看到 destroy 方法的实现。

null

null

        这里可以看到对 Bean 的销毁过程,首先对 postProcessBeforeDestruction 进行调用,然后调用 Bean 的 destroy 方法,最后是对 Bean 的自定义销毁方法的调用,整个过程和前面的初始化过程很类似。

    2.5.2   lazy-init 属性和预实例化

        通过设置 Bean 的 lazy-init 属性来控制预实例化的过程,预实例化在初始化容器时完成 Bean 的依赖注入。这种容器的使用方式会对容器初始化的性能有一些影响,但却能够提高应用第一次取得 Bean 的性能。

        在 refresh()中的代码实现,可以看到预实例化是整个 refresh 初始化 IoC 容器的一个步骤。在 finishBeanFactoryInitialization 的方法中,封装了对 lazy-init 属性的处理,实际的处理是在 DefaultListableBeanFactory 这个基本容器的 preInstantiateSingletons 方法中完成的。该方法对单件 Bean 完成预实例化,这个预实例化的完成巧妙地委托给容器来实现。如果需要预实例化,那么就直接在这里采用 getBean 去触发依赖注入,与正常依赖注入的触发相比,只有触发的时间和场合不同。refresh() 中的预实例化代码如下:

null

null

    2.5.3   FactoryBean 的实现

        FactoryBean 为应用生成需要的对象,这些对象往往是经过特殊处理的,如 ProxyFactoryBean 这样的特殊 Bean。FactoryBean 的生产特性是在 getBean 中起作用的,看下面的调用:

        bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); 

        getObjectForBeanInstance 做了哪些处理?在 getObjectForBeanInstance 的实现方法中可以看到在 FactoryBean 中常见的 getObject 方法的接口,详细的实现过程如代码如下所示:

null

null

null

    2.5.4   BeanPostProcessor 的实现

        BeanPostProcessor 是使用 IoC 容器时经常会遇到的一个特性,这个 Bean 的后置处理器是一个监听器,它可以监听容器触发的事件。将它向 IoC 容器注册后,容器中管理的 Bean 具备了接收 IoC 容器事件回调的能力。BeanPostProcessor 的使用非常简单,只需要通过设计一个具体的后置处理器来实现。同时,这个具体的后置处理器需要实现接口类 BeanPostProcessor,然后设置到 XML 的 Bean 配置文件中。这个 BeanPostProcessor 是一个接口类,它有两个接口方法,一个是 postProcessBeforeInitialization,在 Bean 初始化前提供回调入口;一个是 postProcessAfterInitialization,在 Bean 的初始化后提供回调入口,这两个回调的触发都是和容器管理 Bean 的生命周期相关的。这两个回调方法的参数都是一样的,分别是 Bean 的实例化对象和 Bean 的名字:

null

        对于这些接口是在什么地方与 IoC 结合在一起的,可以查看一下以 getBean 方法为起始的调用关系,其调用过程如图所示:

null

        postProcessBeforeInitialization 是在 populateBean 完成之后被调用的。从 BeanPostProcessor 中的一个回调接口入手,对另一个回调接口 postProcessAfterInitialization 方法的调用,实际上也是在同一个地方封装完成的,这个地方就是 populateBean 方法中的 initializeBean 调用。在前面对 IoC 的依赖注入进行分析时,对这个 populateBean 有过分析,这个方法实际上完成了 Bean 的依赖注入。在容器中建立 Bean 的依赖关系,是容器功能实现的一个很重要的部分。节选 doCreateBean 中的代码就可以看到 postProcessBeforeInitialization 调用和 populateBean 调用的关系,如下所示。

null

        具体的初始化过程也是 IoC 容器完成依赖注入的一个重要部分。在 initializeBean 方法中,需要使用 Bean 的名字,完成依赖注入以后的 Bean 对象,以及这个 Bean 对应的 BeanDefinition。在这些输入的帮助下,完成 Bean 的初始化工作,这些工作包括为类型是 BeanNameAware 的 Bean 设置 Bean 的名字,类型是 BeanClassLoaderAware 的 Bean 设置类装载器,类型是 BeanFactoryAware 的 Bean 设置自身所在的 IoC 容器以供回调使用,当然,还有对 postProcessBeforeInitialization/postProcessAfterInitialization 的回调和初始化属性 init-method 的处理等。经过这一系列的初始化处理之后,得到的结果就是可以正常使用的由 IoC 容器托管的 Bean 了。IoC 容器对 Bean 的初始化,代码如下:

null

null

2.5.5   autowiring(自动依赖装配)的实现

        在前面对 IoC 容器实现原理的分析中,一直是通过 BeanDefinition 的属性值和构造函数以显式的方式对 Bean 的依赖关系进行管理的。在 Spring IoC 容器中还提供了自动依赖装配的方式。在自动装配中,不需要对 Bean 属性做显式的依赖关系声明,只需要配置好 autowiring 属性,IoC 容器会根据这个属性的配置,使用反射自动查找属性的类型或者名字,然后基于属性的类型或名字来自动匹配 IoC 容器中的 Bean,从而自动地完成依赖注入。

        autowiring 属性是在对 Bean 属性进行依赖注入时起作用。对 autowirng 属性进行处理,从而完成对 Bean 属性的自动依赖装配,是在 populateBean 中实现的。对属性 autowiring 的处理是 populateBean 处理过程的一个部分。在 populateBean 的实现中,在处理一般的 Bean 之前,先对 autowiring 属性进行处理。如果当前的 Bean 配置了 autowire_by_name 和 autowire_by_type 属性,那么调用相应的 autowireByName 方法和 autowireByType 方法。例如,对于 autowire_by_name,它首先通过反射机制从当前 Bean 中得到需要注入的属性名,然后使用这个属性名向容器申请与之同名的 Bean,这样实际又触发了另一个 Bean 的生成和依赖注入的过程。populateBean 对 autowiring 属性的处理,代码如下:

null

        在对 autowiring 类型做了一些简单的逻辑判断以后,通过调用 autowireByName 和 autowireByType 来完成自动依赖装配。以 autowireByName 为例子来看看容器的自动依赖装配功能是怎样实现的。对 autowireByName 来说,它首先需要得到当前 Bean 的属性名,这些属性名已经在 BeanWrapper 和 BeanDefinition 中封装好了,然后是对这一系列属性名进行匹配的过程。在匹配的过程中,因为已经有了属性的名字,所以可以直接使用属性名作为 Bean 名字向容器索取 Bean,这个 getBean 会触发当前 Bean 的依赖 Bean 的依赖注入,从而得到属性对应的依赖 Bean。在执行完这个 getBean 后,把这个依赖 Bean 注入到当前 Bean 的属性中去,这样就完成了通过这个依赖属性名自动完成依赖注入的过程。autowireByType 的实现和 autowireByName 的实现过程是非常类似的,感兴趣的读者可以自己进行分析。这些 autowiring 的实现如代码所示:

null

    2.5.6   Bean 的依赖检查

        在使用 Spring 的时候,如果应用设计比较复杂,那么在这个应用中, IoC 管理的 Bean 的个数可能非常多,这些 Bean 之间的相互依赖关系也会非常复杂。在一般情况下,Bean 的依赖注入是在应用第一次向容器索取 Bean 的时候发生,在这个时候,不能保证注入一定能够成功,如果需要重新检查这些依赖关系的有效性,会是一件很繁琐的事情。为了解决这样的问题,在 Spring IoC 容器中,设计了一个依赖检查特性,通过它,Spring 可以帮助应用检查是否所有的属性都已经被正确设置。在具体使用的时候,应用只需要在 Bean 定义中设置 dependency-check 属性来指定依赖检查模式即可,这里可以将属性设置为 none、simple、object、all 四种模式,默认的模式是 none。如果对检查模式进行了设置,通过下面的分析,可以更好地理解这个特性的使用。具体的实现代码是在 AbstractAutowireCapableBeanFactory 实现 createBean 的过程中完成的。在这个过程中,会对 Bean 的 Dependencies 属性进行检查,如果发现不满足要求,就会抛出异常通知应用。

null

    2.5.7   Bean 对 IoC 容器的感知

        容器管理的 Bean 一般不需要了解容器的状态和直接使用容器,但在某些情况下,是需要在 Bean 中直接对 IoC 容器进行操作的,这时候,就需要在 Bean 中设定对容器的感知。Spring IoC 容器也提供了该功能,它是通过特定的 aware 接口来完成的。aware 接口有以下这些:

        BeanNameAware,可以在 Bean 中得到它在 IoC 容器中的 Bean 实例名称。

        BeanFactoryAware,可以在 Bean 中得到 Bean 所在的 IoC 容器,从而直接在 Bean 中使用 IoC 容器的服务。

        ApplicationContextAware,可以在 Bean 中得到 Bean 所在的应用上下文,从而直接在 Bean 中使用应用上下文的服务。

        MessageSourceAware,在 Bean 中可以得到消息源。

        ApplicationEventPublisherAware,在 Bean 中可以得到应用上下文的事件发布器,从而可以在 Bean 中发布应用上下文的事件。

        ResourceLoaderAware,在 Bean 中可以得到 ResourceLoader,从而在 Bean 中使用 ResourceLoader 加载外部对应的 Resource 资源。

    在设置 Bean 的属性之后,调用初始化回调方法之前,Spring 会调用 aware 接口中的 setter 方法。以 ApplicationContextAware 为例,分析对应的设计和实现。这个接口定义得很简单。

null

        这里只有一个方法 setApplicationContext(ApplicationContext applicationContext),它是一个回调函数,在 Bean 中通过实现这个函数,可以在容器回调该 aware 接口方法时使注入的 applicationContext 引用在 Bean 中保存下来,供 Bean 需要使用 ApplicationContext 的基本服务时使用。这个对 setApplicationContext 方法的回调是由容器自动完成的。可以看到,一个 ApplicationContextAwareProcessor 作为 BeanPostProcessor 的实现,对一系列的 aware 回调进行了调用,比如对 ResourceLoaderAware 接口的调用,对 ApplicationEventPublisherAware 接口的调用,以及对 MessageSourceAware 和 ApplicationContextAware 的接口调用等。

null

        而作为依赖注入的一部分,postProcessBeforeInitialization 会在 initializeBean 的实现过程中被调用,从而实现对 aware 接口的相关注入。关于 initializeBean 的详细过程,感兴趣的读者可以参阅前面的章节进行回顾

2.6  小结



第 3 章  Spring AOP 的实现

    本章内容

        Spring AOP 概述、Spring AOP 的设计与实现、建立 AopProxy 代理对象、Spring AOP 拦截器调用的实现、Spring AOP 的高级特性

3.1  Spring AOP 概述

    3.1.1   AOP 概念回顾

        AOP 是 Aspect-Oriented Programming(面向方面编程或面向切面)的简称。

        关于 AOP 技术简介:

            AspectJ:源代码和字节码级别的编织器,用户需要使用不同于 Java 的新语言。

            AspectWerkz:AOP 框架,使用字节码动态编织器和 XML 配置。

            JBoss-AOP:基于拦截器和元数据的 AOP 框架,运行在 JBoss 应用服务器上。以及在 AOP 中用到的一些相关的技术实现:

            BCEL(Byte-Code Engineering Library):Java 字节码操作类库,具体的信息可以参见项目网站http://jakarta.apache.org/bcel/

            Javassist:Java 字节码操作类库,JBoss 的一个子项目,项目信息可以参见项目网站http://jboss.org/javassist/

        AOP 联盟定义的 AOP 体系结构如下图所:

null

AOP 联盟定义的 AOP 体系结构

        最高层是语言和开发环境,“基础”(base)可以视为待增强对象或者说目标对象;“切面”(aspect)通常包含对于基础的增强应用;“配置”(configuration)可以看成是一种编织,通过在 AOP 体系中提供这个配置环境,可以把基础和切面结合起来,从而完成切面对目标对象的编织实现。

        第二个层次面向方面系统,在 Spring AOP 实现中,使用 Java 语言来实现增强对象与切面增强应用,并为二者结合提供配置环境。对于编织配置,可以使用 IoC 容器来完成;对于 POJO 对象的配置,本来就是 Spring 的核心 IoC 容器的强项。AOP 体系结构的第二个层次是为语言和开发环境提供支持的,在这个层次中可以看到 AOP 框架的高层实现,主要包括配置和编织实现两部分内容。

        最底层是编织的具体实现模块,图中各种技术都可作为编织逻辑的具体实现方法,比如反射、程序预处理、拦截器框架、类装载器框架、元数据处理等。在 Spring AOP 中,使用的是 Java 本身的语言特性,如 Java Proxy 代理类、拦截器等技术,来完成 AOP 编织的实现。

    3.1.2   Advice 通知

        Advice(通知)定义在连接点做什么,为切面增强提供织入接口。主要描述 Spring AOP 围绕方法调用而注入的切面行为。Advice 是 AOP 联盟定义的一个接口,具体的接口定义在 org.aopalliance.aop.Advice 中。具体的通知类型实现有:BeforeAdvice、AfterAdvice、ThrowsAdvice 等。从接口 BeforeAdvice 开始,首先了解它的类层次关系,如下图:

null

BeforeAdvice 的类层次关系

        在BeforeAdvice的继承关系中,定义了为待增强的目标方法设置的前置增强接口 MethodBeforeAdvice,使用这个前置接口需要实现一个回调函数:  voidbefore(Method method, Object[] args, Object target) throws Throwable;

        在 Advice 的实现体系中,Spring 还提供了AfterAdvice这种通知类型,它的类接口关系如图所示:

null

AfterAdvice 的类层次关系

        在图中的 AfterAdvice 类接口关系中,可以看到一系列对 AfterAdvice 的实现和接口扩展,如:AfterReturningAdvice 就是其中比较常用的一个。以AfterReturningAdvice通知的实现为例,分析一下 AfterAdvice 通知类型的实现原理。在 AfterReturning-Advice 接口中定义了接口方法,如下所示: voidafterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;

      在 Spring AOP 中,还可以看到另外一种 Advice 通知类型,那就是ThrowsAdvice,它的类层次关系如图所示:

null

ThrowsAdvice 的类层次关系

        对于 ThrowsAdvice,并没有指定需要实现的接口方法,它在抛出异常时被回调,这个回调是 AOP 使用反射机制来完成的。

    3.1.3   Pointcut 切点

        Pointcut(切点)决定 Advice 通知应该作用于哪个连接点,也就是说通过 Pointcut 来定义需要增强的方法的集合,这些集合的选取可以按照一定的规则来完成。在这种情况下,Pointcut 通常意味着标识方法,例如,这些需要增强的地方可以由某个正则表达式进行标识,或根据某个方法名进行匹配等。切点在 Spring AOP 中的类继承体系如图所示:

null

切点在 Spring AOP 中的类继承体系结构

        在 Pointcut 的基本接口定义中可以看到,需要返回一个 MethodMatcher。对于 Point 的匹配判断功能,具体是由这个返回的 MethodMatcher 来完成的,也就是说,由这个 MethodMatcher 来判断是否需要对当前方法调用进行增强,或者是否需要对当前调用方法应用配置好的 Advice 通知。在 Pointcut 的类继承关系中,以正则表达式切点 JdkRegexpMethodPointcut 的实现原理为例,来具体了解切点 Pointcut 的工作原理。在 JdkRegexpMethodPointcut 的基类 StaticMethodMatcherPointcut 的实现中可以看到,设置 MethodMatcher 为 StaticMethodMatcher,同时 JdkRegexpMethodPointcut 也是这个 MethodMatcher 的子类,它的类层次关系如图:

null

Spring AOP 的 Pointcut 类继承关系

null

StaticMethodMatcherPointcut 的类继承关系

        在 Pointcut 的类继承关系中,MethodMatcher 对象实际上是可以被配置成 JdkRegexpMethodPointcut 来完成方法的匹配判断的。在 JdkRegexpMethodPointcut 中,可以看到一个matches方法,这个 matches 方法是 MethodMatcher 定义的接口方法。在 JdkRegexpMethodPointcut 的实现中,这个 matches 方法就是使用正则表达式来对方法名进行匹配的地方。对 matches 方法的调用关系如图所示:

null

JdkRegexpMethodPointcut 的 matches  方法调用关系

        在 JdkDynamicAopProxy 的 invoke 方法中触发了对 matches 方法的调用。这里重点了解 Pointcut 的实现原理,比如 matches 本身的实现。 JdkRegexpMethodPointcut 的 matches 方法的实现,代码清单如下所示。

null

JdkRegexpMethodPointcut 使用 matches  完成匹配

        在 Spring AOP 中,还提供了其他的 MethodPointcut,比如通过方法名匹配进行 Advice 匹配的 NameMatchMethodPointcut。它的 matches 方法实现很简单,匹配的条件是方法名相同或者方法名相匹配,如代码清单所示:

null

    3.1.4   Advisor 通知器

        完成对目标方法的切面增强设计(Advice)和关注点的设计(Pointcut)以后,需要一个对象把它们结合起来,完成这个作用的就是 Advisor(通知器)。通过 Advisor,可以定义应该使用哪个通知并在哪个关注点使用它。在 Spring AOP 中,我们以一个 Advisor 的实现(DefaultPointcutAdvisor)为例,来了解 Advisor 的工作原理。在 DefaultPointcutAdvisor 中,有两个属性,分别是 advice 和 pointcut。通过这两个属性,可以分别配置 Advice 和 Pointcut,DefaultPointcutAdvisor 的实现如代码清单所示:

null

        在 DefaultPointcutAdvisor 中,pointcut 默认被设置为 Pointcut.True,这个 Pointcut.True 在 Pointcut 接口中被定义为:Pointcut TRUE = TruePointcut.INSTANCE;    TruePointcut 的 INSTANCE 是一个单件。在 TruePointcut 的 methodMatcher 实现中,使用 TrueMethodMatcher 作为方法匹配器。这个方法匹配器对任何的方法匹配都要求返回 true 的结果,也就是说对任何方法名的匹配要求,它都会返回匹配成功的结果。和 TruePointcut 一样,TrueMethodMatcher 也是一个单件实现。TruePointcut 和 TrueMethodMatcher 的实现如代码清单所示:

null

null

3.2   Spring AOP 的设计与实现

    3.2.1   JVM 的动态代理特性

        在 Spring AOP 实现中,使用的核心技术是动态代理,而这种动态代理实际上是 JDK 的一个特性(在 JDK 1.3 以上的版本里,实现了动态代理模式)。通过 JDK 的动态代理特性,可以为任意 Java 对象创建代理对象,对于具体使用来说,这个特性是通过 Java Reflection API 来完成的。

        JDK 中已经实现了Proxy 模式,在基于 Java 虚拟机设计应用程序时,只需要直接使用这个特性就可以了。具体来说,可以在 Java 的 reflection 包中看到 Proxy 对象,这个对象生成后,所起的作用就类似于 Proxy 模式中的 Proxy 对象。在使用时,还需要为代理对象(Proxy)设计一个回调方法,这个回调方法起到的作用是,在其中加入了作为代理需要额外处理的动作。在 JDK 中实现,需要实现下面所示的InvocationHandler接口:

null

        invoke 方法的第一个参数是代理对象实例,第二个参数是 Method 方法对象,代表的是当前 Proxy 被调用的方法,最后一个参数是被调用的方法中的参数。至于怎样让 invoke 方法和 Proxy 挂上钩,熟悉 Proxy 用法的读者都知道,只要在实现通过调用 Proxy.newIntance 方法生成具体 Proxy 对象时把 InvocationHandler 设置到参数里面就可以了,剩下的由 Java 虚拟机来完成。

    3.2.2   Spring AOP 的设计分析

        **Spring AOP 的核心技术是 JDK 动态代理技术。** 以动态代理技术为基础,设计出了一系列 AOP 的横切实现,比如前置通知、返回通知、异常通知等。同时,Spring AOP 还提供了一系列的 Pointcut 来匹配切入点,可以使用现有的切入点来设计横切面,也可以扩展相关的 Pointcut 方法来实现切入需求。

        在 Spring AOP 中,虽然对于 AOP 的使用者来说,只需要配置相关的 Bean 定义即可,但仔细分析 Spring AOP 的内部设计可以看到,为了让 AOP 起作用,需要完成一系列过程,比如,需要为目标对象建立代理对象,这个代理对象可以通过使用 JDK 的 Proxy 来完成,也可以通过第三方的类生成器 cglib 来完成。然后,还需要启动代理对象的拦截器来完成各种横切面的织入,这一系列的织入设计是通过一系列 Adapter 来实现的。通过一系列 Adapter 的设计,可以把 AOP 的横切面设计和 Proxy 模式有机地结合起来,从而实现在 AOP 中定义好的各种织入方式。

    3.2.3   Spring AOP 的应用场景

        Spring AOP 为 IoC 的使用提供了更多的便利,一方面,应用可以直接使用 AOP 的功能,设计应用的横切关注点,把跨越应用程序多个模块的功能抽象出来,并通过简单的 AOP 的使用,灵活地编制到模块中,比如可以通过 AOP 实现应用程序中的日志功能。另一方面,在 Spring 内部,一些支持模块也是通过 Spring AOP 来实现的,比如后面将要详细介绍的事务处理。从这两个角度就已经可以看到 Spring AOP 的核心地位了。下面以 ProxyFactoryBean 的实现为例,和大家一起来了解 Spring AOP 的具体设计和实现。

3.3  建立 AopProxy 代理对象

    3.3.1 设计原理

        在 Spring 的 AOP 中,一个主要的部分是代理对象的生成,而对于 Spring 应用,是通过配置和调用 Spring 的ProxyFactoryBean来完成这个任务。在 ProxyFactoryBean 中,封装了主要代理对象的生成过程。在这个过程中,可以使用JDK 的 ProxyCGLIB两种生成方式。

        以 ProxyFactory 的设计为中心,可以看到相关的类继承关系如图所示:

null

类继承关系

        在这个类继承关系中,可以看到完成 AOP 应用的类,比如 AspectPxoxyFactory、ProxyFactory 和 ProxyFactoryBean,都在同一个类的继承体系下,都是 ProxyConfig、AdvicedSupport 和 ProxyCreatorSupport 的子类。作为共同基类 ProxyConfig 为 ProxyFactoryBean 这样的子类提供了配置属性;在另一个基类 AdvisedSupport 的实现中,封装了 AOP 对通知后台通知器的相关操作,这些操作对于不同的 AOP 的代理对象的生成都是一样的,但对于具体的 AOP 代理对象的创建,AdvisedSupport 把它交给它的子类们去完成;对于 ProxyCreatorSupport,可以将它看成是其子类创建 AOP 代理对象的一个辅助类。通过继承以上提到的基类的功能实现,具体的 AOP 代理对象的生成,根据不同的需要,分别由 ProxyFactoryBean、AspectJProxyFactory 后台 ProxyFactory 来完成。对于需要使用 AspectJ 的 AOP 应用,AspectJProxyFactory 起到集成 Spring 和 AspectJ 的作用;对于使用 Spring AOP 的应用,ProxyFactoryBean(可在 IoC 容器中完成声明式配置)和 ProxyFactory(需要编程式使用 SpringAOP)都提供了 AOP 功能的封装。

    3.3.2 配置 ProxyFactoryBean

       ** 以 ProxyFactoryBean 为例,分析 Spring AOP 的实现原理,**ProxyFactoryBean 是在 SpringIoC 环境中创建 AOP 应用的底层方法,是一个非常灵活的创建 AOP 应用的底层方法,Spring 通过它完成了对 AOP 使用的封装。

null

    3.3.3 ProxyFactoryBean 生成 AopProxy 代理对象

        从 ProxyFactoryBean 的简单配置例子可看出,ProxyFactoryBean 是用来配置目标对象和切面行为 Advice 的,ProxyFactoryBean 通过其配置的拦截器名称 interceptorNames 即通知器 Advisor 将切面行为 Advice 应用到目标对象中。

        在 ProxyFactoryBean 中,需要为待增强目标对象目标对象生成 Proxy 代理对象,从而为 AOP 切面的编织提供基础,AOP Proxy 的生成有两种方式, 依赖 JDK 或者 CGLIB 提供的 Proxy 特性, 代理生成过程如下图:

null

AopProxy 的生成过程

        initializeAdvisorChain():初始化通知器链, 通知器链中封装了一系列从配置中读取的拦截器, 为代理对象的生成做好准备。 

        getSingletonInstance():生成 Singleton 类型的 Proxy 。

        DefaultAopProxyFactory:AopProxy 接口类,AopProxy 有两个子类实现, 一个是 JdkDynamicAopProxy, 一个是 CglibProxyFactory 。

        从 FactoryBean 中获取对象是以 getObject 方法作为入口完成的,ProxyFactoryBean 实现中的 getObject()方法,是 FactoryBean 需要实现的接口。对 ProxyFactoryBean 来说,需要对目标对象增加的增强处理,都通过了 getObject 方法进行了封装,这些增强处理是为 AOP 功能的实现提供服务的。getObject 的实现清单如下。getObect() 方法首先对通知器进行了初始化,通知器封装了一系列的拦截器,这些拦截器都要从配置文件中获取,然后为代理对象的生成做好准备。在生成代理对象时,因为 Spring 中有 singleton 类型和 prototype 类型这两种不同的 bean,所有要对代理对象进行一个区分。

null

        为 Proxy 代理对象配置 Advisor 链是在 initializeAdvisorChain 方法中实现的。这个初始化过程中有一个标志位 AdvisorChainInitialized,这个标志用来表示通知器是否已经初始化。如果已经初始化,那么这里就会在初始化,而是直接返回。也就说,这个初始化的工作发生在应用第一次通过 ProxyFactoryBean 去获取代理对象的时候。在完成这个初始化之后,接着读取配置中出现的所有通知器,这个取得通知器的过程也比较简单,把通知器的名字交给容器的 getBean 方法就可以了,这是通过对 IOC 容器实现的一个回调完成的。然后把从 IOC 容器中取得的通知器加入到拦截器链中,这个动作是由 addAdvisorOnChainCreation 方法来实现的。

null

null

null

        接着在 getObject()方法中,将会执行 getSingletonInstance() 方法,该方法主要是生成代理对象并封装对 target 目标对象的调用(即加入拦截器)。具体的生成过程是,首先读取 ProxyFactoryBean 中的配置,为生成代理对象做好必要的准备,比如设置代理的方法接口调用等。Spring 通过 AopProxy 类来具体生成代理对象。对于 getSingletonInstance() 方法中代理对象的生成过程,代码清单如下:

null

        这里出现了AopProxy对象类型,Spring 利用 AopProxy 接口类把 AOP 代理对象的实现与框架其他部分有效隔离开来。AopProxy 接口有两个子类实现,一个Cglib2AopProxy,另一个是JdkDynamicProxy。 具体代理对象的生成是在 ProxyFactoryBean 的基类 AdvisedSupport 中实现,借助 AopProxyFactory 完成,这个对象要么从 JDK 中生成,要么借助 CGLIB 获得。下面看看 ProxyCreatorSupport 中是如何生成代理对象的。

null

        AopProxy 代理对象的生成有两种方式,如果目标对象是接口类使用 JDK 来生成,否则 Spring 会使用 CGLIB 来生成目标的代理对象。下面看看在 DefaultAopProxyFactory 是如何生成 AopProxy 目标代理对象的:

null

        在 AopProxy 代理对象的生成过程中,首先要从 AdviseSupport 对象中取得配置的目标对象,AOP 完成的是切面应用对目标应用对象的增强。如果这里没有配置目标对象会直接抛出异常。一般而言,默认方式是使用 JDK 来产生 AopProxy 代理对象,但如果配置的目标对象不是接口类的实现,会使用 CGLIB 来产生 AopProxy 代理对象;在使用 CGLIB 来产生 AopProxy 代理对象时,因为 CGLIB 是第三方类库,本身不在 JDK 基类库中,所有需要在 classPath 中正确配置,以便能够加载和利用。在 Spring 中,使用 JDK 和 CGLIB 来生成 AopProxy 代理对象的工作,是由 JdkDynamicAopProxy 和 CglibProxyFactory 来完成。

    3.3.4. JDK 生成 AopProxy 代理对象 

        通过上面我们已经知道生成 AopProxy 对象有两种方式,下面看下类图:

null

        JdkDynamicAopProxy 生成代理对象:

null

        newProxyInstance 方法: 需要指明 3 个参数, 类装载器, 代理接口,Proxy 回调方法所在的对象, 这个对象要实现 InvocationHandler 接口 .InvocationHandler 接口: 反射类接口, 定义了 invoke 方法, 提供代理对象的回调入口。 

    3.3.5. CGLIB 生成 AopProxy 代理对象 

null

null

null

        配置 Enhancer 对象, 通过 Enhancer 对象的 callback 回调设置生成代理对象。其中通过设置 DynamicAdvisedInterceptor 拦截器来完成 AOP 功能的。

3.4 Spring AOP 拦截器调用的实现 

    3.4.1、设计原理 

        在 Spring AOP 通过 JDK 的 Proxy 方式或 CGLIB 方式生成代理对象的时候,相关的拦截器已经配置到代理对象中去了,拦截器在代理对象中起作用是通过对这些方法的回调来完成的。 如果使用 JDK 的 Proxy 来生成代理对象,那么需要 InvocationHandler 来设置拦截器回调,而如果使用 CGLIB 来生成代理对象,通过 DynamicAdvisedInterceptor 来完成回调。 

    3.4.2、JdkDynamicAopProxy 的 invoke 拦截 

        在 JDKDynamicAopProxy 生成代理对象时,它的 AopProxy 代理对象生成调用:

                        Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);

        this 指的是 InvocationHandler 对象,InvocationHandler 是 JDK 定义反射类的一个接口,这个接口定义了 invoke 方法,此方法为回调方法。通过 invoke 的具体实现,来完成对目标对象方法调用的拦截器或者功能增强工作。在这个方法中,包含一个完整的拦截器链对目标对象的拦截过程,比如获取拦截器链中的拦截器进行配置,逐个运行拦截器链里的拦截器增强,知道最后的目标对象方法的运行。下面看下 invoke 的源码

null

null

    3.4.3 CglibAopProxy 的 intercept 拦截器 

        CglibAopProxy 实现 DynamicAdcisedIntercepte 接口,该接口有 intercept()方法。

null

    3.3.4、目标方法的调用 

    如果没有拦截器会对目标对象方法直接调用。对于 JDKDynamicAopProxy 代理对象是通过 AopUtils 使用反射机制实现的。在这个调用方法中首先得到调用方法的反射对象,然后使用 invoke 启动对方法反射对象的调用。源码如下:

null

 对于使用 Cglib2AopProxy 的代理对象,其目标对象的调用是通过 CGLIB 的 MethodProxy 对象直接完成。retVal=methodProxy.invoke(target,args);

    3.4.5   AOP 拦截器的调用 

        下面进入 AOP 的核心部分,Aop 是怎样完成对目标的增强的。这些都封装在 Aop 拦截器链中,由一个个具体的拦截器完成。 无论是使用 JDKDynamicAopProxy 还是使用 CglibAopProxy 创建代理对象最终对 AOP 拦截链的调用都是在 ReflectiveMethodInvocation 中通过 proceed 方法实现的。在 proceed 方法中逐个运行拦截器的拦截方法。在运行拦截器的拦截方法之前需要对代理方法完成一个匹配,通过这个匹配判断来决定拦截器是否满足切面增强的要求。具体代码如下:

null

    3.4.6、配置通知器 

        在整个 AopProxy 代理对象拦截回调过程中,先回到 ReflectionMethodInvocation 类的 proceed 方法,在这个方法里,可以看到得到了配置的 interceptorOrInterceptionAdvice,如下所示:Object interceptorOrInterceptionAdvice =

                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);

        interceptorOrInterceptionAdvice 是获得的拦截器,它通过拦截器机制对目标对象进行增强。这个拦截器来自 interceptorsAndDynamicMethodMatchers。具体来说,他是 interceptorsAndDynamicMathers 持有的 List 中的一个元素。关于如何配置拦截器的问题就转化为了 List 中的拦截器元素是从哪里来的,在哪里配置的问题。接着对 invoke 调用进行回放,回到 JDKDynamicAopProxy 中的 invoke 方法中,可以看到这个 List 中的 interceptors 是从哪个调用中获取的。对于 CglibAopProxy,也有类似过程,只不过这个过程是在 DynamicAdvisedInterceptor 的 intercept 回调中实现的,如下所示:

                List chain =this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass);

        从上面的代码可以看出,获取 intercptors 的操作是由 advised 对象完成的,这个 advised 是一个 AdvisedSupport 对象,从 AdvisedSupport 类中可以看到 getInterceptorsAndDynamicInterceptionAdvice 的实现。在这个方法中取得了拦截器链,再取得拦截器链的时候,为了提高拦截器链的效率,还为这个拦截器链这是了缓存。

null

        获取拦截器的工作由配置好的 advisorChainFactory 来完成,advisorChainFactory 被配置成 DefaultAdvisorChainFactory,在 DefaultAdvisorChainFactory 中实现了 interceptor 链的获取过程,在这个过程中,首先设置了一个 List,其长度是配置的通知器的个数来决定的,这个配置时在 XML 中对 ProxyFactoryBean 做的 interceptNames 属性的配置,然后,DefaultAdvisorChainFactory 会通过一个 AdvisorAdapterRegistry 来实现拦截器的注册。AdvisorAdapterRegistry 对 advice 通知的织入功能起了很大作用。有了 AdvisorAdapterRegistry 注册器,利用他来对 ProxyFactoryBean 配置中得到的通知进行适配,从而得到相应的拦截器,再把他前面设置好的 List 中去,完成所谓的拦截器注册过程。在拦截器适配和注册过程完成以后,List 中的拦截器会被 JDK 生成的 AopProxy 代理对象的 invoke 方法或者 CGLIB 代理对象的 intercept 拦截方法获得,并启动拦截器的 invoke 方法调用,最终触发通知的切面增强。 

        下面看看 DefaultAdvisorChainFactory 是怎样生成拦截器链的:

null

null

        在 ProxyFactoryBean 的 getObject 方法中对 adviosr 进行初始化,从 XML 配置中获取了 advisor 通知器。下面看下在 ProxyFactoryBean 拦截器链的初始化中获取 advisor 通知器

null

null

        advisor 通知器的取得时委托给 IOC 容器完成的,但是在 ProxyFactoryBean 中是如何获得 IOC 容器,然后通过回调 IOC 容器的 getBean 方法来得到需要的通知 advisor?在这里大家可以回顾下 IOC 容器的原理。 

    3.4.7、Advice 通知的实现 

        AopProxy 代理对象生成时,其拦截器也一并生成。下面我们来分析下 Aop 是如何对目标对象进行增强的。在为 AopProxy 配置拦截器的实现中,有一个取得拦截器配置过程,这个过程由 DefaultAvisorChainFactory 实现的,而这个工厂类负责生成拦截器链,在它的 getInterceptorsAndDynamicInterceptionAdvice 方法中,有一个适配器的注册过程,通过配置 Spring 预先设计好的拦截器,Spring 加入了它对 Aop 实现的处理。为详细了解这个过程,先从 DefaultAdvisorChainFactory 的实现开始,通过以下代码可以看到,在 DefaultAdvisorChainFactory 实现中,首先构造了一个 GlobalAdvisorAdapterRegistry 单件,然后对配置的 Advisor 通知器进行逐个遍历,这些通知链都是配置在 interceptorNames 中的,从 getInterceptorsAndDynamicInterceptionAdvice 传递进来的 advised 参数对象中,可以方便的取得配置的通知器,有了这些通知器,接着就是一个由 GlobalAdvisorAdapterRegistry 来完成的拦截器的适配和注册。

null

null

        GlobalAdvisorAdapterRegistry 的 getInterceptors 方法为 AOP 的实现做出了很大的贡献,这个方法封装着 advice 织入实现的入口,我们先从 GlobalAdvisorAdapterRegistry 的实现入手,他基本起一个适配器的作用,但同时也是单件模式,代码如下:

null

        到这里,我们知道在 DefaultAdvisorAdapterRegistry 中,设置了一系列的 adapter 适配器,这是这些适配器的实现,为 Spring 的 advice 提供了编织能力,下面我们看看 DefaultAdvisorAdapterRegistry 究竟发生了什么?adapter 的作用具体分为两个: 

        1、调用 adapter 的 support 方法,通过这个方法来判断取得的 advice 属于什么类型的 advice 通知,从而根据不同的 advice 类型来注册不同的 AdviceInterceptor,也就是前面我们看到的拦截器 

        2、这些 AdviceInterceptor 都是 Spring AOP 框架设计好的,是为实现不同的 advice 功能提供服务的。有了这些 AdviceInterceptor,可以方便的使用由 Spring 提供的各种不同的 advice 来设计 AOP 应用。也就是说,正是这些 AdviceInterceptor 最终实现了 advice 通知在 AopProxy 对象中的织入功能。

null

null

        剥茧抽丝,继续看 adapter,在 DefaultAdvisorRegistry 的 getInterceptors 调用中,从 MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter、ThrowsAdviceAdaper 这几个通知适配器的名字上可以看出和 Advice 一一对应,在这里,他们作为适配器被加入到 adapter 的 List 中,他们都是实现 AdvisorAdapter 接口的同一层次的类,只是各自承担着不同的适配的任务,一对一的服务于不同的 advice 实现。以 MethodBeforeAdviceAdapter 为例,代码如下:

null

        到这里就非常清楚了,Spring AOP 为了实现 advice 的织入,设计了特定拦截器对这些功能进行了封装。虽然应用不会直接用到这些拦截器,但却是 advice 发挥作用不可缺少的准备。还是以 MethodBeforeAdviceInterceptor 为例,我们看看 advice 是如何封装的。在 invoke 回调方法中,看到首先触发了 advice 的 before 的回调,然后才是 MethodInvocation 的 proceed 方法的调用。看到这里,就已经和前面的在 ReflectionMethodInvocation 的 Proceed 分析中联系起来。回忆了一下,在 AopProxy 代理对象触发的 ReflectionMethodInvocation 的 proceed 方法中,在取得拦截器以后,启动了对拦截器 invoke 方法的调用。按照 AOP 的规则,ReflectiveMethodInvocation 触发的拦截器 invoke 方法,最终会根据不同的 advice 类型,触发 Spring 对不同的 advice 的拦截器封装,比如对 MethodBeforeAdvice, 最终会根据不同的 advice 类型触发 Spring 对不同的 advice 的拦截器封装。比如对 MethodBeforeAdvice,最终会触发 MethodBeforeAdviceInterceptor 的 invoke 方法。在 MethodBeforeAdviceInterceptor 方法中,会调用 advice 的 before 方法,这就是 MethodBeforeAdvice 所需要的对目标对象的增强效果:在方法调用之前通知增强。

null

        完成 MethodBeforeAdviceInterceptor 的调用,然后启动 advice 通知的 afterReturning 回调,代码如下:

null

        ThrowsAdvice 的实现和上面类似,也是封装在对应的 AdviceInterceptor 中,ThrowsAdvice 的回调方法要复杂一些,他维护了一个 exceptionHandlerMap 来对应不同的方法调用场景,这个 exceptionHandlerMap 中的 handler 的取得时与触发 ThrowsAdvice 增强的异常相关的。

null

null

null

    3.4.8 ProxyFactory 实现 AOP

        除了使用 ProxyFactory Bean 实现 AOP 之外,还可以使用 ProxyFactory 编程式的完成 AOP 应用的设置。下面举一个使用 ProxyFactory 的例子:


            TargetImpl  target =new TargetImpl();

            ProxyFactory aopFactory =new ProxyFactory(target);

            aopFactory.addAdvisor(yourAdvisor);

            aopFactory.addAdvice(yourAdvice);

           TargetImpl targetProxy =(TargetImpl )new aopFactory.getProxy();


public class  ProxyFactory   extends   ProxyCreatorSupport{

            public Object getProxy() {

                       return createAopProxy().getProxy();

            }

}

3.5 Spring AOP 的高级特性

    略

3.6  小结


第二部分  Spring 组件实现篇

第 4 章  SpringMVC 与 Web 环境

  **  本章内容 **

        Spring MVC 概述、Web 环境中的 SpringMVC、上下文在 Web 容器中的启动、Spring MVC 的设计与实现、Spring MVC 视图的呈现

4.1 Spring MVC 概述

null

MVC 模式

        在使用 Spring MVC 的时候,需要在 web.xml 中配置 DispatcherServlet,这个 DispatcherServlet 可以看成是一个前端控制器的具体实现,还需要在 Bean 定义中配置 Web 请求和 Controller(控制器)的对应关系,以及各种视图的展现方式。在具体使用 Controller 的时候,会看到 ModelAndView 数据的生成,还会看到把 ModelAndView 数据交给相应的 View 来进行呈现。

4.2 Web 环境中的 Spring MVC

        Spring MVC 是建立在 IoC 容器基础上的,要了解 SpringMVC,首先要了解 Spring IoC 是如何在 Web 环境中发挥作用的。

        Spring IoC 是个独立的模块,并不是直接在 Web 环境中发挥作用,在 web 容器启动过程中,将 IoC 导入,并在 web 容器中建立起来并初始化,这样才能建立起 SpringMVC 运行机制,从而响应 web 容器传递的 HTTP 请求。

        Tomcat 的 web.xml 对 Spring MVC 的配置:

null

        在这个配置描述中,首先定义一个 Servlet 对象,它是 Spring MVC 的 DispatcherServlet。这个 DispatcherServlet 是 MVC 中最重要的一个类,起着分发请求的作用。然后,为这个 DispatcherServlet 定义了对应的 URL 映射,这些 URL 映射为这个 Servlet 指定了需要处理的 HTTP 请求。context-param 参数的配置用来指定 Spring IOC 容器读取 Bean 定义的 XML 文件的路径,在这里,这个配置文件被定义为 /WEBINF/applicationContext.xml。DispatcherServlet 和 ContextLoaderListener 提供了在 Web 容器中对 Spring 的接口,这些接口与 web 容器耦合是通过 ServletContext 来实现的。

4.3 上下文在 Web 容器中的启动

4.3.1 IOC 容器启动的基本过程

        IoC 容器启动过程就是建立上下文的过程,该上下文与 ServletContext 相伴而生,由 ContextLoaderListener 启动的上下文为根上下文。在根上下文的基础上,还有一个与 Web MVC 相关的上下文用来保存控制器(DispatcherServlet)需要的 MVC 对象,作为跟上下文的子上下文。

        Web 容器中启动 Spring 应用程序的过程如下图:

null

  Web 容器中启动 Spring 应用程序的过程

        在 web.xml 中,已经配置了由 Spring 提供的实现了 ServletContextListener 接口的 ContextLoaderListener,该监听器类为在 Web 容器中建立 IoC 容器提供服务。在 Web 容器中,建立 WebApplicationContext 的过程,是在 contextInitialized 的接口实现中完成的。具体的载入 IOC 容器的过程是由 ContextLoaderListenser 交由 ContextLoader 来完成的,而 ContextLoader 本身就是 ContextLoaderListener 的基类,它们之间的类关系为:

null

        在 ContextLoader 中,完成两个 IoC 容器建立的基本过程,一个是在 Web 容器中建立起双亲 IoC 容器,另一个是生成相应的 WebApplicationContext 并将其初始化。

4.3.2 Web 容器中的上下文设计

        WebApplicationContext 接口的类继承关系图:

null

null

WebApplicationContext

        在启动过程中,Spring 使用默认的 XmlWebApplicationContext 实现作为 IoC 容器。

null

null

4.3.3 ContextLoader 的设计与实现

        对于 Spring 承载的 Web 应用而言,可以指定在 Web 应用程序启动时载人 IoC 容器 (或者称为 WebAppl icationCon text)。这个功能是由 ContextLoaderListener 这样的类来完成的,它是在 Web 容器中配置的监听器。这个 ContextLoaderListener 通过使用 ContextLoader 来完成实际的 WebApplicationContext,也就是 IoC 容器的初始化工作。这个 ContextLoader 就像 Spring 应用程序在 Web 容器中的启动器。这个启动过程是在 Web 容器中发生的,所以需要根据 Web 容器部署的要求来定义 ContextLoader。

        下面分析具体的根上下文的载入过程。在 ContextLoaderListener 中,实现的是 ServletContextListener 接口,这个接口里的函数会结合 Web 容器的生命周期被调用。因为 ServletContextListener 是 ServletContext 的监听者,如果 ServletContext 发生变化. 会触发出相应的事件,而监听器一直在对这些事件进行监听,如果接收到了监听的事件,就会做出预先设计好的响应动作。由于 ServletContext 的变化而触发的监听器的响应具体包括:在服务器启动时,ServletContext 被创建。服务器关闭时,ServletContext 将被销毁等。对应这些事件及 Web 容器状态的变化,在监听器中定义了对应的事件响应的回调方法。比如在服务器启动时,ServletContextListener 的 contextlnitialized()方法被调用,服务器将要关闭时,ServletContextListener 的 contextDestroyed() 方法被调用。

null

null

null

        在初始化这个上下文以后,该上下文会被存储到 SevletContext 中,这样就建立了一个全局的关于整个应用的上下文。同时,在启动 Spring MVC 时. 我们还会看到这个上下文被以后的 DispatcherServlet 在进行自己持有的上下文的初始化时,设置为 DispatcherServlet 自带的上下文的双亲上下文。

4.4 Spring MVC 的设计与实现

4.4.1  Spring MVC 的应用场景

        在前文的分析过程中,了解了 Spring 的上下文体系通过 ContextLoader 和 DispatcherServiet 建立并初始化的过程。在完成对 ContextLoaderListener 的初始化以后,Web 容器开始初始化 DispatcherServlet,这个初始化的启动与在 web.xml 中对载入次序的定义有关。

        DispatcherServiet 会建立自己的上下文来持有 Spring MVC 的 Bean 对象,在建立这个自己持有的 Ioc 容器时,会从 ServletContext 中得到根上下文作为 DispatcherServlet 持有上下文的双亲上下文。有了这个根上下文,再对自己持有的上下文进行初始化,最后把自己持有的这个上下文保存到 ServletContext(Web 容器的上下文)中,供以后检索和使用。

4.4.2  Spring MVC 设计概览

null

DispacherServlet 类的继承关系

        DispatcherServlet 的处理过程,如下图所示:

null

   DispatcherServlet 的处理过程

        DispatcherServiet 的工作大致可以分为两个部分:一个是初始化部分,由 initServletBean()启动,通过 initWebAppIicationContext() 方法最终调用 DispatcherServlet 的 initStrategies 方法。在这个方法里,DispatcherServlet 对 MVC 模块的其他部分进行了初始化,比如 handlerMapping, ViewResolver 等。另一个是对 HTTP 请求进行响应,作为一个 Serviet,Web 容器会调用 Servlet 的 doGet()和 doPost() 方法,在经过 FrameworkServlet 的 processRequest()简单处理后,会调用 DispatcherServlet 的 doService() 方法,在这个方法调用中封装了 doDispatch(),这个 doDispatch() 是 Dispatcher 实现 MVC 模式的主要部分。

4.4.3 DispatcherServlet 的启动和初始化

       作为 Servlet,DispatcherServlet 的启动与 Servlet 的启动过程是相联系的。在 Servlet 的初始化过程中,Servlet 的 init 方法会被调用。在 HttpServletBean 中进行初始化:

null

        FrameworkServlet 中的初始化方法:

null

null

null

        由于这个根上下文是 DispatcherServlet 建立的上下文的双亲上下文,所以根上下文中粉理的 Bean 也是可以被 DispatcherServlet 的上下文使用的。通过 getBean 向 IoC 容器获取 Bean 时. 容器会先到它的双亲 IoC 容器中获取 getBean。除了上面的 SpringMVC 上下文的的创建之外,还需要启动 SpringMVC 中的其他一些配置初始化,通过上面的 onRefresh 调用来完成,这个方法在子类 DispatcherServlet 中被覆盖了,实际调用了 initStrategies 进行配置,这里就不细说这些配置了。 源码如下:

null

        对于具体的初始化过程,根据上面的方法名称,很容易理解。以 HanderMapping 为例来说明这个 initHanderMappings 过程。这里的 Mapping 关系的作用是,为 HTTP 请求找到相应 的 Controller 控制器,从而利用这些控制器 Controller 去完成设计好的数据处理工作。HandlerMappings 完成对 MVC 中 Controller 的定义和配置,只不过在 Web 这个特定的应用环境中,这些控制器是与具体的 HTTP 请求相对应的。DispatcherServlet 中 HandlerMappings 初始化过程的具体实现如下。在 HandlerMapping 初始化的过程中,把在 Bean 配置文件中配置好的 handlerMapping 从 IoC 容器中取得。

null

        经过以上的读取过程,handlerMappings 变量就已经获取了在 BeanDefinition 中配置好的映射关系。其他的初始化过程和 handlerMappings 比较类似,都是直接从 IoC 容器中读入配置,所以这里的 MVC 初始化过程是建立在 IoC 容器已经初始化完成的基础上的。至于上下文是如何获得的,可以参见前面对 IoC 容器在 Web 环境中加载的实现原理的分析。

4.4.4  MVC 处理 HTTP 分发请求

    1.HandlerMapping 的配置和设计原理

      在初始化完成时,在上下文环境中已定义的所有 HandlerMapping 都已经被加载了,这些加载的 handlerMappings 被放在一个 List 中并排序,存储着 HTTP 请求对应的映射数据。这个 List 中的每一个元素都对应着一个具体 handlerMapping 的配置,一般每一个 handlerMapping 可以持有一系列从 URL 请求到 Controller 的映射,而 Spring MVC 提供了一系列的 HandlerMapping 实现。

null

HandlerMapping 设计原理

null

        这个 HandlerExecutionChain 的实现看起来比较简洁,它持有一个拦截器链和一个 handler 对象,这个 handler 对象实际上就是 HTTP 请求对应的 Controller,在持有这个 handler 对象的同时,还在 HandlerExecutionChain 中设置了拦截器链,通过这个拦截器链中的拦截器,可以为 handler 对象提供功能的增强。要完成这些工作,需要对拦截器链和 handler 都进行配置,这些配置都是在这个类的初始化函数中完成的。为了维护这个拦截器和 handler, HandlerExecutionChain 还提供了一系列与拦截器链维护相关一些操作,比如可以为拦截器链增加拦截器的 addInterceptor 方法等。

null

null

null

null

        HandlerExecutionChain 中定义的 Handler 和 Interceptor 需要在定义 HandlerMapping 时配置好,例如对具体的 SimpleURLHandlerMapping, 要做的就是根据 URL 映射的方式,注册 Handler 和 Interceptor,从而维护一个反映这种映射关系的 handlerMap。当需要匹配 HTTP 请求时,需要查询这个 handlerMap 中信息来得到对应的 HandlerExecutionChain。这些信息是什么时候配置好的呢?这里有一个注册过程,这个注册过程在容器对 Bean 进行依赖注入时发生,它实际上是通过一个 Bean 的 postProcessor 来完成的。如果想了解这个过程,可以从依赖注入那里去看看,doCreateBean->initializeBean -> postProcessBeforeInitialization -> setApplicationContext -> initApplicationContext.

    以 SimpleUrlHandlerMapping 为例,需要注意的是,这里用到了对容器的回调,只有 SimpleUrlHandlerMapping 是 AppicationContextAware 的子类才能启动这个注册过程。这个注册过程完成的是反映 URL 和 Controller 之间的映射关系的 handlerMap 的建立。具体分析如下:

null

        这个 SimpleUrlHandlerMapping 注册过程的完成,很大一部分需要它的基类来配合,这个基类就是 AbstractUrlHandlerMapping.

null

null

      这个配置好 URL 请求和 handler 映射数据的 handlerMap,为 Spring MVC 响应 HTTP 请求准备好了基本的映射数据,根据这个 handlerMap 以及设置于其中的映射数据,可以方便地由 URL 请求得到它所对应的 handler。有了这些准备工作,Spring MVC 就可以等待 HTTP 请求的到来了。

2  使用 HandlerMapping 完成请求的映射处理

    继续通过 SimpleUrlHandlerMapping 的实现来分析 HandlerMapping 的接口方法 getHandler,该方法会根据初始化时得到的映射关系来生成 DispatcherServlet 需要的 HandlerExecutionChain,也就是说,这个 getHandler 方法是实际使用 HandlerMapping 完成请求的映射处理的地方。

null

null

        取得 handler 的具体过程在 getHandlerInternal 方法中实现,这个方法接受 HTTP 请求作为参数,它的实现在 AbstractHandlerMapping 的子类 AbstractUrlHandlerMapping 中,这个实现过程包括从 HTTP 请求中得到 URL,并根据 URL 到 urlMapping 中获得 handler。代码实现如下:

null

null

null

null

        经过这一系列对 HTTP 请求进行解析和匹配 handler 的过程,得到了与请求对应的 handler 处理器。在返回的 handler 中,已经完成了在 HandlerExecutionChain 中的封装工作,为 handler 对 HTTP 请求的响应做好了准备。然后,在 MVC 中,还有一个重要的问题:请求是怎样实现分发,从而到达对应的 handler 的呢?

3.Spring MVC 对 HTTP 请求的分发处理

    重新回到 DispatcherServlet, 这个类不但建立了自己持有的 IoC 容器,还肩负着请求分发处理的重任。在 MVC 框架初始化完成以后,对 HTTP 请求的处理是在 doService() 方法中完成的,DispatcherServlet 也是通过这个方法来响应 HTTP 的请求。然后,依据 Spring MVC 的使用,业务逻辑的调用入口是在 handler 的 handler 函数中实现的,这里是连接 Spring MVC 和应用业务逻辑实现的地方。

null

null

null

        经过上面一系列的处理,得到了 handler 对象,接着就可以开始调用 handler 对象中的 HTTP 响应动作了。在 handler 中封装了应用业务逻辑,由这些逻辑对 HTTP 请求进行相应的处理,生成各种需要的数据,并把这些数据封装到 ModelAndView 对象中去,这个 ModelAndView 的数据封装是 Spring MVC 框架的要求。对 handler 来说,这些都是通过调用 handler 的 handlerRequest 方法来触发完成的。在得到 ModelAndView 对象以后,这个 ModelAndView 对象会被交给 MVC 模式中的视图类,由视图类对 ModelAndView 对象中的数据进行呈现。视图呈现的调用入口在 DispatcherServlet 的 doDispatch 方法中实现,它的调用入口是 render 方法。这个方法在下面介绍。

4.5、Spring MVC 视图的呈现

4.5.1.DispatcherServlet 视图呈现的设计

    前面分析了 Spring MVC 中的 M(Model) 和 C(Controller)相关的实现,其中的 M 大致对应成 ModelAndView 的生成,而 C 大致对应到 DispatcherServlet 和用户业务逻辑有关的 handler 实现。在 Spring MVC 框架中,DispatcherServlet 起到了非常杧的作用,是整个 MVC 框架的调用枢纽。对于下面关心的视图呈现功能,它的调用入口同样在

null

        DispatcherServlet 中的 doDispatch 方法中实现。具体来说,在 DispatcherServlet 中,对视图呈现的处理是在 render 方法调用中完成的,代码如下。为了完成视图的呈现工作,需要从 ModelAndViewc 对象中取得视图对象,然后调用视图对象的 render 方法,由这个视图对象来完成特定的视图呈现工作。同时,由于是在 Web 的环境中,因此视图对象的呈现往往需要完成与 HTTP 请求和响应相关的处理,这些对象会作为参数传到视图对象的 render 方法中,供 render 方法使用

null

null

null

null



第 5 章  数据库操作组件的实现

    本章内容

        Spring JDBC 的设计与实现、Spring JDBC 中模板类的设计与实现、Spring JDBC 中 RDBMS 操作对象的实现、Spring ORM 的设计与实现

5.1  Spring JDBC 的设计与实现

5.1.1  应用场景

        Spring 建立的 JDBC 的框架中,还设计了一种更面向对象的方法,相对于 JDBC 模板,这种实现更像是一个简单的 ORM 工具,为应用提供了另外一种选择。

5.1.2 设计概要

    Spring JDBC 提供了一系列的模板类作为应用提供便利, 在这其中运用到了 GOF 设计模式中的模板模式, 如下图:

null

模板设计模式

        在 Spring 设计的模板中,大部分封装了对 JDBC 和 Hibernate 处理的通用过程,比如数据库资源管理、Hibernate 的 session 管理等,在使用时,只需要设计自己定制化的或者和应用相关的部分就可以了。

5.2 SpringJDBC 中模板类的设计与实现

5.2.1  设计原理

        在 Spring JDBC 中,JdbcTemplate 是一个主要的模板类,继承关系如图:

null

JdbcTemplate 的类图

        从类继承关系上来看,JdbcTemplate 继承了基类 JdbcAccessor 的和接口类 JdbcAperation。在基类 JdbcAccessor 的设计中,对 DataSource 数据源进行管理和配置。在 JdbcOperation 接口中,定义了通过 JDBC 操作数据库的基本操作方法,而 Jdbctemplate 提供这些接口方法的实现,比如 execute 方法、query 方法和 update 方法等。

5.2.2  JdbcTemplate 的基本使用

        在模板的回调方法 doInStatement 中嵌入的是用户对数据库进行操作的代码,可以由 Spring 来完成,或者由客户应用直接来完成,然后通过 JdbcTemplate 的 execute 方法就可以完成相应的数据库操作。

5.2.3  JdbcTemplate 的 execute 实现

null

5.2.4  JdbcTemplate 的 query 实现

null

        query 方法是通过使用 PreparedStatementCallback 的回调方法 doInPreparedStatement 来实现的。在回调函数中,可以看到 PreparedStatement 的执行,以及查询结果的返回处理结果。

5.2.5  使用数据库 Connection

        在以上这些对数据库的操作中,使用了辅助类 DataSourceUtils。对于 DataSource 缓冲池的实现,用户可以通过定义 Apache Jakarta Commons DBCP 或者 C3P0 提供的 DataSource 来完成,然后在 IOC 容器中配置好后交给 JdbcTemplate 就可以使用了。

5.3  Spring JDBC 中 RDBMS 操作对象的实现

        Spring 提供了 org.springframework.jdbc.object 包,其中包含了 SqlQuery、SqlMappingQuery、SqlUpdate 和 StoredProcedure 等类,这些类都是 Spring JDBC 应用程序可以使用的。

        RDBMS 对象的基本继承关系

null

5.3.1  SqlQuery 的实现

在使用 MappingSqlQuery 完成这个数据转换功能的时候,需要用户扩展一个 MappingSqlQuery 实现,并在用户扩展类的初始化函数中对 SQL 查询语句和查询参数进行设置,然后调用 compile 来完成这些设置。这部分数据转换代码会在对数据库的查询结束执行,从而完成数据查询记录到 Java 数据对象的转换。

null

5.3.2  SqlUpdate 的实现

        主要提供数据的更新功能。

        SqlUpdate 的使用非常简洁,对应用来说,只需要提供具体的参数对象的值,并调用 update 方法就可以完成整个数据的更新过程,至于数据库 Connection 的管理、事务处理场景的处理等在数据库操作中都会涉及的基本过程,由作为应用平台的 Spring 来处理。SqlUpdate 的设计时序

null

        SqlUpdate 里的 updateByNameParam 方法完成的,使用 JdbcTemplate 来完成的。

5.3.3 SqlFunction

        SqlFunction 类是 MappingSqlQuery 的子类。SqlFunction 基本功能:SqlFunction RDBMS 操作类中封装了一个 SQL“函数”包装器(wrapper),使用这个包装器可以查询并返回一个单行结果集,对于这个单行结果集,SqlFunction 默认的返回对象是一个 int 值。

        为了使用 SqlFunction,首先要创建一个 SqlFunction 对象,创建时需要为它指定数据源和执行的 Sql 语句。创建完以后,执行 compile,然后可以调用 SqlFunction 的 run 方法来完成指定的 SQL 语句的执行,从而得到查询数据记录行数的返回结果。

        SqlFunction 的设计时序

null

5.4  Spring ORM 的设计与实现

5.4.1 应用场景

        Spring 的 ORM 包提供了对许多 ORM 产品的支持。

5.4.2 设计概要

        应用通过 Spring 使用这些 ORM 工具时,通常使用 Spring 提供的 Template 类(模板类)。在这些模板类里,封装了主要的数据操作方法,比如 query、update 等,并且在 Template 封装中,已经包含了前面所说的通用过程,这些通用过程包括 Hibernate 中的 Session 的处理、Connection 的处理、事务的处理等。

        以 Template 为核心的类设计

null



第 6 章  Spring 事务处理的实现

    本章内容

        Spring 与事务处理、Spring 事务处理的设计概览、Spring 事务处理的应用场景、Spring 声明式事务处理、Spring 事务处理的设计与实现 Spring 事务处理器的设计与实现

6.1  Spring 与事务处理

6.2  Spring 事务处理的设计概览

null

6.3  Spring 事务处理的应用场景

6.4  Spring 声明式事务处理

6.4.1  设计原理与基本过程

        Spring 事务处理主要分为以下三个主要的过程:

            (1)读取和处理 Spring IoC 容器中配置的事务处理属性,并转化为 Spring 事务处理所需要的内部数据结构。

            (2)Spring 事务处理模块实现的统一的事务处理过程。这个统一的事务处理过程包括:处理事务配置属性、事务配置属性与线程绑定等。

            (3)底层事务处理实现。Spring 中底层事务处理的实现交由 PlatformTransactionManager 的具体实现类来实现,如 DataSourceTransactionManager 和 HibernateTransactionManager 等。

6.4.2  实现分析

    声明式事务配置:

null

完结……