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

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


第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整体架构

《Spring技术内幕 第2版》学习笔记_第1张图片
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容器系列概况:

《Spring技术内幕 第2版》学习笔记_第2张图片
Spring的IoC容器系列概况

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

    2.2.2  Spring 的IoC容器的设计

《Spring技术内幕 第2版》学习笔记_第3张图片
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本身。

《Spring技术内幕 第2版》学习笔记_第4张图片
BeanFactory接口

        2、BeanFactory容器的设计原理

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

《Spring技术内幕 第2版》学习笔记_第5张图片
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实现:

《Spring技术内幕 第2版》学习笔记_第6张图片
  XmlBeanFactory实现

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

《Spring技术内幕 第2版》学习笔记_第7张图片
编程式使用IoC容器

        步骤:

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

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

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

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

        3、ApplicationContext的应用场景

        ApplicationContext的接口关系:

《Spring技术内幕 第2版》学习笔记_第8张图片
 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()过程。

《Spring技术内幕 第2版》学习笔记_第9张图片
FileSystemXmlApplicationContext 

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

《Spring技术内幕 第2版》学习笔记_第10张图片
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 定位的,下面是这个类对应的继承体系。

《Spring技术内幕 第2版》学习笔记_第11张图片

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

《Spring技术内幕 第2版》学习笔记_第12张图片
源码角度FileSystemXmlApplicationContext 的继承关系

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

《Spring技术内幕 第2版》学习笔记_第13张图片
FileSystemXmlApplicationContext源代码

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

          资源定位时序图如下:

《Spring技术内幕 第2版》学习笔记_第14张图片
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数据,初始化入口函数:

《Spring技术内幕 第2版》学习笔记_第15张图片
启动BeanDefinition的载入

         IoC容器的refresh过程代码:

《Spring技术内幕 第2版》学习笔记_第16张图片
IoC容器的refresh过程代码

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

        AbstractRefreshableApplicationContext 中的refreshBeanFactory() :

《Spring技术内幕 第2版》学习笔记_第17张图片
AbstractRefreshableApplicationContext 的refreshBeanFactory  ()

        AbstractXmlApplicationContext的loadBeanDefinitions():

《Spring技术内幕 第2版》学习笔记_第18张图片

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

《Spring技术内幕 第2版》学习笔记_第19张图片

        AbstractBeanDefinitionReader载入BeanDefinition:

《Spring技术内幕 第2版》学习笔记_第20张图片

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

《Spring技术内幕 第2版》学习笔记_第21张图片
《Spring技术内幕 第2版》学习笔记_第22张图片
《Spring技术内幕 第2版》学习笔记_第23张图片

      BeanDefinition载入时序图:

《Spring技术内幕 第2版》学习笔记_第24张图片
            BeanDefinition载入时序图

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

《Spring技术内幕 第2版》学习笔记_第25张图片

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

        创建BeanDefinitionDocumentReader:

《Spring技术内幕 第2版》学习笔记_第26张图片

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

《Spring技术内幕 第2版》学习笔记_第27张图片
《Spring技术内幕 第2版》学习笔记_第28张图片

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

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

《Spring技术内幕 第2版》学习笔记_第29张图片
《Spring技术内幕 第2版》学习笔记_第30张图片

        上面是具体生成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注册时序图:

《Spring技术内幕 第2版》学习笔记_第31张图片
注册的调用过程

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

《Spring技术内幕 第2版》学习笔记_第32张图片
《Spring技术内幕 第2版》学习笔记_第33张图片
BeanDefinition的注册

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

2.4  IoC容器的依赖注入

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

《Spring技术内幕 第2版》学习笔记_第34张图片
《Spring技术内幕 第2版》学习笔记_第35张图片
《Spring技术内幕 第2版》学习笔记_第36张图片
《Spring技术内幕 第2版》学习笔记_第37张图片

        依赖注入的大致过程:

《Spring技术内幕 第2版》学习笔记_第38张图片
依赖注入的大致过程

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

《Spring技术内幕 第2版》学习笔记_第39张图片
《Spring技术内幕 第2版》学习笔记_第40张图片
《Spring技术内幕 第2版》学习笔记_第41张图片
《Spring技术内幕 第2版》学习笔记_第42张图片

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

《Spring技术内幕 第2版》学习笔记_第43张图片
《Spring技术内幕 第2版》学习笔记_第44张图片

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

《Spring技术内幕 第2版》学习笔记_第45张图片
使用SimpleInstantiationStrategy生成Java对象

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

《Spring技术内幕 第2版》学习笔记_第46张图片
《Spring技术内幕 第2版》学习笔记_第47张图片
《Spring技术内幕 第2版》学习笔记_第48张图片
《Spring技术内幕 第2版》学习笔记_第49张图片
《Spring技术内幕 第2版》学习笔记_第50张图片

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

《Spring技术内幕 第2版》学习笔记_第51张图片
《Spring技术内幕 第2版》学习笔记_第52张图片
BeanDefinitionValueResolver

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

《Spring技术内幕 第2版》学习笔记_第53张图片
《Spring技术内幕 第2版》学习笔记_第54张图片
《Spring技术内幕 第2版》学习笔记_第55张图片
《Spring技术内幕 第2版》学习笔记_第56张图片

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

《Spring技术内幕 第2版》学习笔记_第57张图片
《Spring技术内幕 第2版》学习笔记_第58张图片
《Spring技术内幕 第2版》学习笔记_第59张图片
《Spring技术内幕 第2版》学习笔记_第60张图片
《Spring技术内幕 第2版》学习笔记_第61张图片
《Spring技术内幕 第2版》学习笔记_第62张图片
《Spring技术内幕 第2版》学习笔记_第63张图片

        这样就完成了对各种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,容器自身也有一个初始化和销毁关闭的过程。下面详细看看在这两个过程中,应用上下文完成了什么,可以让我们更多地理解应用上下文的工作,容器初始化和关闭过程可以简要地通过下图表现:

《Spring技术内幕 第2版》学习笔记_第64张图片
IoC容器初始化和关闭过程

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

《Spring技术内幕 第2版》学习笔记_第65张图片

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

《Spring技术内幕 第2版》学习笔记_第66张图片

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

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

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

《Spring技术内幕 第2版》学习笔记_第67张图片

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

《Spring技术内幕 第2版》学习笔记_第68张图片

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

《Spring技术内幕 第2版》学习笔记_第69张图片

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

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

《Spring技术内幕 第2版》学习笔记_第70张图片

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

《Spring技术内幕 第2版》学习笔记_第71张图片
《Spring技术内幕 第2版》学习笔记_第72张图片

        这里可以看到对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()中的预实例化代码如下:

《Spring技术内幕 第2版》学习笔记_第73张图片
《Spring技术内幕 第2版》学习笔记_第74张图片

    2.5.3   FactoryBean的实现

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

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

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

《Spring技术内幕 第2版》学习笔记_第75张图片
《Spring技术内幕 第2版》学习笔记_第76张图片
《Spring技术内幕 第2版》学习笔记_第77张图片

    2.5.4   BeanPostProcessor的实现

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

《Spring技术内幕 第2版》学习笔记_第78张图片

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

《Spring技术内幕 第2版》学习笔记_第79张图片

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

《Spring技术内幕 第2版》学习笔记_第80张图片

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

《Spring技术内幕 第2版》学习笔记_第81张图片
《Spring技术内幕 第2版》学习笔记_第82张图片

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属性的处理,代码如下:

《Spring技术内幕 第2版》学习笔记_第83张图片

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

《Spring技术内幕 第2版》学习笔记_第84张图片

    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属性进行检查,如果发现不满足要求,就会抛出异常通知应用。

《Spring技术内幕 第2版》学习笔记_第85张图片

    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为例,分析对应的设计和实现。这个接口定义得很简单。

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

《Spring技术内幕 第2版》学习笔记_第86张图片

        而作为依赖注入的一部分,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体系结构如下图所:

《Spring技术内幕 第2版》学习笔记_第87张图片
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开始,首先了解它的类层次关系,如下图:

《Spring技术内幕 第2版》学习笔记_第88张图片
BeforeAdvice的类层次关系

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

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

《Spring技术内幕 第2版》学习笔记_第89张图片
AfterAdvice的类层次关系

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

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

《Spring技术内幕 第2版》学习笔记_第90张图片
ThrowsAdvice的类层次关系

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

    3.1.3   Pointcut切点

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

《Spring技术内幕 第2版》学习笔记_第91张图片
切点在Spring AOP中的类继承体系结构

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

《Spring技术内幕 第2版》学习笔记_第92张图片
Spring AOP的Pointcut类继承关系
《Spring技术内幕 第2版》学习笔记_第93张图片
StaticMethodMatcherPointcut的类继承关系

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

《Spring技术内幕 第2版》学习笔记_第94张图片
JdkRegexpMethodPointcut的matches  方法调用关系

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

《Spring技术内幕 第2版》学习笔记_第95张图片
JdkRegexpMethodPointcut使用matches  完成匹配

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

《Spring技术内幕 第2版》学习笔记_第96张图片

    3.1.4   Advisor通知器

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

《Spring技术内幕 第2版》学习笔记_第97张图片

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

《Spring技术内幕 第2版》学习笔记_第98张图片
《Spring技术内幕 第2版》学习笔记_第99张图片

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接口:

《Spring技术内幕 第2版》学习笔记_第100张图片

        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的设计为中心,可以看到相关的类继承关系如图所示:

《Spring技术内幕 第2版》学习笔记_第101张图片
类继承关系

        在这个类继承关系中,可以看到完成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使用的封装。

《Spring技术内幕 第2版》学习笔记_第102张图片

    3.3.3 ProxyFactoryBean生成AopProxy代理对象

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

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

《Spring技术内幕 第2版》学习笔记_第103张图片
AopProxy的生成过程

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

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

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

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

《Spring技术内幕 第2版》学习笔记_第104张图片

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

《Spring技术内幕 第2版》学习笔记_第105张图片
《Spring技术内幕 第2版》学习笔记_第106张图片
《Spring技术内幕 第2版》学习笔记_第107张图片

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

《Spring技术内幕 第2版》学习笔记_第108张图片

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

《Spring技术内幕 第2版》学习笔记_第109张图片

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

《Spring技术内幕 第2版》学习笔记_第110张图片

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

    3.3.4. JDK生成AopProxy代理对象 

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

《Spring技术内幕 第2版》学习笔记_第111张图片

        JdkDynamicAopProxy生成代理对象:

《Spring技术内幕 第2版》学习笔记_第112张图片

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

    3.3.5. CGLIB生成AopProxy代理对象 

《Spring技术内幕 第2版》学习笔记_第113张图片
《Spring技术内幕 第2版》学习笔记_第114张图片

        配置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的源码

《Spring技术内幕 第2版》学习笔记_第115张图片
《Spring技术内幕 第2版》学习笔记_第116张图片

    3.4.3 CglibAopProxy的intercept拦截器 

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

《Spring技术内幕 第2版》学习笔记_第117张图片

    3.3.4、目标方法的调用 

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

《Spring技术内幕 第2版》学习笔记_第118张图片

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

    3.4.5   AOP拦截器的调用 

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

《Spring技术内幕 第2版》学习笔记_第119张图片

    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的实现。在这个方法中取得了拦截器链,再取得拦截器链的时候,为了提高拦截器链的效率,还为这个拦截器链这是了缓存。

《Spring技术内幕 第2版》学习笔记_第120张图片

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

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

《Spring技术内幕 第2版》学习笔记_第121张图片
《Spring技术内幕 第2版》学习笔记_第122张图片

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

《Spring技术内幕 第2版》学习笔记_第123张图片
《Spring技术内幕 第2版》学习笔记_第124张图片

        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来完成的拦截器的适配和注册。

《Spring技术内幕 第2版》学习笔记_第125张图片
《Spring技术内幕 第2版》学习笔记_第126张图片

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

《Spring技术内幕 第2版》学习笔记_第127张图片

        到这里,我们知道在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对象中的织入功能。

《Spring技术内幕 第2版》学习笔记_第128张图片
《Spring技术内幕 第2版》学习笔记_第129张图片

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

《Spring技术内幕 第2版》学习笔记_第130张图片

        到这里就非常清楚了,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所需要的对目标对象的增强效果:在方法调用之前通知增强。

《Spring技术内幕 第2版》学习笔记_第131张图片

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

《Spring技术内幕 第2版》学习笔记_第132张图片

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

《Spring技术内幕 第2版》学习笔记_第133张图片
《Spring技术内幕 第2版》学习笔记_第134张图片
《Spring技术内幕 第2版》学习笔记_第135张图片

    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概述


《Spring技术内幕 第2版》学习笔记_第136张图片
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的配置:

《Spring技术内幕 第2版》学习笔记_第137张图片

        在这个配置描述中,首先定义一个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应用程序的过程如下图:

《Spring技术内幕 第2版》学习笔记_第138张图片
  Web容器中启动Spring应用程序的过程

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

《Spring技术内幕 第2版》学习笔记_第139张图片

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

4.3.2 Web容器中的上下文设计

        WebApplicationContext接口的类继承关系图:

《Spring技术内幕 第2版》学习笔记_第140张图片


《Spring技术内幕 第2版》学习笔记_第141张图片
WebApplicationContext

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

《Spring技术内幕 第2版》学习笔记_第142张图片
《Spring技术内幕 第2版》学习笔记_第143张图片

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()方法被调用。

《Spring技术内幕 第2版》学习笔记_第144张图片
《Spring技术内幕 第2版》学习笔记_第145张图片
《Spring技术内幕 第2版》学习笔记_第146张图片

        在初始化这个上下文以后,该上下文会被存储到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设计概览

《Spring技术内幕 第2版》学习笔记_第147张图片
DispacherServlet类的继承关系

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

《Spring技术内幕 第2版》学习笔记_第148张图片
   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中进行初始化:

《Spring技术内幕 第2版》学习笔记_第149张图片

        FrameworkServlet中的初始化方法:

《Spring技术内幕 第2版》学习笔记_第150张图片
《Spring技术内幕 第2版》学习笔记_第151张图片
《Spring技术内幕 第2版》学习笔记_第152张图片

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

《Spring技术内幕 第2版》学习笔记_第153张图片

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

《Spring技术内幕 第2版》学习笔记_第154张图片

        经过以上的读取过程,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实现。

《Spring技术内幕 第2版》学习笔记_第155张图片
HandlerMapping设计原理


《Spring技术内幕 第2版》学习笔记_第156张图片

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

《Spring技术内幕 第2版》学习笔记_第157张图片
《Spring技术内幕 第2版》学习笔记_第158张图片
《Spring技术内幕 第2版》学习笔记_第159张图片
《Spring技术内幕 第2版》学习笔记_第160张图片

        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的建立。具体分析如下:

《Spring技术内幕 第2版》学习笔记_第161张图片

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

《Spring技术内幕 第2版》学习笔记_第162张图片
《Spring技术内幕 第2版》学习笔记_第163张图片

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

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

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

《Spring技术内幕 第2版》学习笔记_第164张图片
《Spring技术内幕 第2版》学习笔记_第165张图片


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

《Spring技术内幕 第2版》学习笔记_第166张图片
《Spring技术内幕 第2版》学习笔记_第167张图片
《Spring技术内幕 第2版》学习笔记_第168张图片
《Spring技术内幕 第2版》学习笔记_第169张图片

        经过这一系列对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和应用业务逻辑实现的地方。

《Spring技术内幕 第2版》学习笔记_第170张图片
《Spring技术内幕 第2版》学习笔记_第171张图片
《Spring技术内幕 第2版》学习笔记_第172张图片

        经过上面一系列的处理,得到了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框架的调用枢纽。对于下面关心的视图呈现功能,它的调用入口同样在

《Spring技术内幕 第2版》学习笔记_第173张图片

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

《Spring技术内幕 第2版》学习笔记_第174张图片
《Spring技术内幕 第2版》学习笔记_第175张图片
《Spring技术内幕 第2版》学习笔记_第176张图片
《Spring技术内幕 第2版》学习笔记_第177张图片


第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设计模式中的模板模式,如下图:

《Spring技术内幕 第2版》学习笔记_第178张图片
模板设计模式

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

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

5.2.1  设计原理

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

《Spring技术内幕 第2版》学习笔记_第179张图片
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实现

《Spring技术内幕 第2版》学习笔记_第180张图片

5.2.4  JdbcTemplate的query实现

《Spring技术内幕 第2版》学习笔记_第181张图片

        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对象的基本继承关系

《Spring技术内幕 第2版》学习笔记_第182张图片

5.3.1  SqlQuery的实现

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

《Spring技术内幕 第2版》学习笔记_第183张图片

5.3.2  SqlUpdate的实现

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

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

《Spring技术内幕 第2版》学习笔记_第184张图片

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

5.3.3 SqlFunction

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

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

        SqlFunction的设计时序

《Spring技术内幕 第2版》学习笔记_第185张图片

5.4  Spring ORM的设计与实现

5.4.1应用场景

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

5.4.2设计概要

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

        以Template为核心的类设计

《Spring技术内幕 第2版》学习笔记_第186张图片




第6章  Spring事务处理的实现

    本章内容

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

6.1  Spring与事务处理

6.2  Spring事务处理的设计概览

《Spring技术内幕 第2版》学习笔记_第187张图片

6.3  Spring事务处理的应用场景

6.4  Spring声明式事务处理

6.4.1  设计原理与基本过程

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

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

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

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

6.4.2  实现分析

    声明式事务配置:

《Spring技术内幕 第2版》学习笔记_第188张图片

完结……

你可能感兴趣的:(《Spring技术内幕 第2版》学习笔记)