今天是:
带着程序的旅程,每一行代码都是你前进的一步,每个错误都是你成长的机会,最终,你将抵达你的目的地。
title

Spring技术内幕

  • Spring 设计主线

IOC容器的两条主线

    从接口BeanFactory到HierarchicalBeanFactory,再到ConfigurableBeanFactory,是一条主要的BeanFactory设计路径。在这条接口设计路径中,BeanFactory接口定义了基本的IoC容器的规范。在这个接口定义中,包括了getBean()这样的IoC容器的基本方法(通过这个方法可以从容器中取得Bean)。而HierarchicalBeanFactory接口在继承了BeanFactory的基本接口之后,增加了getParentBeanFactory()的接口功能,使BeanFactory具备了双亲IoC容器的管理功能。在接下来的ConfigurableBeanFactory接口中,主要定义了一些对BeanFactory的配置功能,比如通过setParentBeanFactory()设置双亲IoC容器,通过addBeanPostProcessor()配置Bean后置处理器,等等。通过这些接口设计的叠加,定义了BeanFactory就是简单IoC容器的基本功能。


       第二条接口设计主线是,以ApplicationContext应用上下文接口为核心的接口设计,这里涉及的主要接口设计有,从BeanFactory到ListableBeanFactory,再到ApplicationContext,再到我们常用的WebApplicationContext或者ConfigurableApplicationContext接口。我们常用的应用上下文基本上都是ConfigurableApplicationContext或者WebApplicationContext的实现。在这个接口体系中,ListableBeanFactory和HierarchicalBeanFactory两个接口,连接BeanFactory接口定义和ApplicationConext应用上下文的接口定义。在ListableBeanFactory接口中,细化了许多BeanFactory的接口功能,比如定义了getBeanDefinitionNames()接口方法;对于HierarchicalBeanFactory接口,我们在前文中已经提到过;对于ApplicationContext接口,它通过继承MessageSource、ResourceLoader、ApplicationEventPublisher接口,在BeanFactory简单IoC容器的基础上添加了许多对高级容器的特性的支持。

 

XmlBeanFactory ,ApplicationContext

1.IoC容器的初始化过程

简单来说,IoC容器的初始化是由前面介绍的refresh()方法来启动的,这个方法标志着IoC容器的正式启动。具体来说,这个启动包括BeanDefinition的Resouce定位、载入和注册三个基本过程

1)BeanDefinition的Resouce定位

FileSystemXmlApplicationContext的实现原理为例子,了解了Resource定位问题的解决方案,即以FileSystem方式存在的Resource的定位实现。在BeanDefinition定位完成的基础上,就可以通过返回的Resource对象来进行BeanDefinition的载入了。在定位过程完成以后,为BeanDefinition的载入创造了I/O操作的条件,但是具体的数据还没有开始读入。

2)BeanDefinition的载入和解析

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

经过对xml逐层地解析,我们在XML文件中定义的BeanDefinition就被整个载入到了IoC容器中,并在容器中建立了数据映射。在IoC容器中建立了对应的数据结构,或者说可以看成是POJO对象在IoC容器中的抽象,这些数据结构可以以AbstractBeanDefinition为入口,让IoC容器执行索引、查询和操作。简单的POJO操作背后其实蕴含着一个复杂的抽象过程,经过以上的载入过程,IoC容器大致完成了管理Bean对象的数据准备工作(或者说是初始化过程)。但是,重要的依赖注入实际上在这个时候还没有发生,现在,在IoC容器BeanDefinition中存在的还只是一些静态的配置信息。严格地说,这时候的容器还没有完全起作用,要完全发挥容器的作用,还需完成数据向容器的注册。

BeanDefinition信息已经在IoC容器内建立起了自己的数据结构以及相应的数据表示,但此时这些数据还不能供IoC容器直接使用,需要在IoC容器中对这些BeanDefinition数据进行注册。这个注册为IoC容器提供了更友好的使用方式,在DefaultListableBeanFactory中,是通过一个HashMap来持有载入的BeanDefinition的。完成了BeanDefinition的注册,就完成了IoC容器的初始化过程。

3)IoC容器的依赖注入

SimpleInstantiationStrategy类。这个Strategy是Spring用来生成Bean对象的默认类,它提供了两种实例化Java对象的方法,一种是通过BeanUtils(通过Constructor来实例化Bean),它使用了JVM的反射功能,一种是通过前面提到的CGLIB来生成。

在Bean的创建和对象依赖注入的过程中,需要依据BeanDefinition中的信息来递归地完成依赖注入。从上面的几个递归过程中可以看到,这些递归都是以getBean为入口的。一个递归是在上下文体系中查找需要的Bean和创建Bean的递归调用;另一个递归是在依赖注入时,通过递归调用容器的getBean方法,得到当前Bean的依赖Bean,同时也触发对依赖Bean的创建和注入。在对Bean的属性

进行依赖注入时,解析的过程也是一个递归的过程。这样,根据依赖关系,一层一层地完成Bean的创建和注入,直到最后完成当前Bean的创建。有了这个顶层Bean的创建和对它的属性依赖注入的完成,意味着和当前Bean相关的整个依赖链的注入也完成了。

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


IOC小结:❍BeanDefinition的定位。对IoC容器来说,它为管理POJO之间的依赖关系提供了帮助,但也要依据Spring的定义规则提供Bean定义信息。我们可以使用各种形式的Bean定义信息,其中比较熟悉和常用的是使用XML的文件格式。在Bean定义方面,Spring为用户提供了很大的灵活性。在初始化IoC容器的过程中,首先需要定位到这些有效的Bean定义信息,这里Spring使用Resource接口来统一这些Bean定义信息,而这个定位由ResourceLoader来完成。如果使用上下文,ApplicationContext本身就为客户提

供了定位的功能。因为上下文本身就是DefaultResourceLoader的子类。如果使用基本的BeanFactory作为IoC容器,客户需要做的额外工作就是为BeanFactory指定相应的Resource来完成Bean信息的定位。

❍容器的初始化。在使用上下文时,需要一个对它进行初始化的过程,完成初始化以后,这个IoC容器才是可用的。这个过程的入口是在refresh中实现的,这个refresh相当于容器的初始化函数。在初始化过程中,比较重要的部分是对BeanDefinition信息的载入和注册工作。相当于在IoC容器中需要建立一个BeanDefinition定义的数据映像,S p r i n g为了达到载入的灵活性,把载入的功能从IoC容器中分离出来,由BeanDefinition R e a d e r来完成B e a n定义信息的读取、解析和IoC容器内部BeanDefinition的建立。在DefaultListableBeanFactory中,这些BeanDefinition被维护在一个Hashmap中,以后的IoC容器对Bean的管理和操作就是通过这些BeanDefinition来完成的。

在容器初始化完成以后,IoC容器的使用就准备好了,但这时只是在IoC容器内部建立了BeanDefinition,具体的依赖关系还没有注入。在客户第一次向IoC容器请求Bean时,IoC容器对相关的Bean依赖关系进行注入。如果需要提前注入,客户可以通过lazy-init属性进行预实例化,这个预实例化是上下文初始化的一部分,起到提前完成依赖注入的控制作用。在依赖注入完成以后,IoC容器就会保持这些具备依赖关系的Bean供客户直接使用。这时可以通过getBean来取得Bean,这些Bean不是简单的Java对

象,而是已经包含了对象之间依赖关系的Bean,尽管这些依赖注入的过程对用户来说是不可见的。

在对IoC容器的分析中,重点讲解了BeanFactory和ApplicationContext体系、ResourceLoader、refresh初始化、容器的loadBeanDefinition和注册、容器的依赖注入、预实例化和FactoryBean的工作原理,等等。通过对这些实现过程的深入分析,我们可以初步了解IoC容器的基本工作原理和它的基本特性的实现思路。了解了IoC容器的基本实现原理后,我们对容器的其他特性的实现原理也进行了分析。这些特性包括init-lazy预实例化、BeanFactory、Bean后置处理器以及autowiring特性的实现。这些特性对我们更灵活地使用IoC容器有很大的帮助。但是,由于Spring IoC容器的内涵特性非常丰富,这里并没有对其工作原理进行面面俱到的分析,如果读者感兴趣,可以参考本章的分析方法和思路,对自己感兴趣的内容继续进行分析。

2.Spring AOP实现

AOP是Aspect-Oriented Programming(面向方面编程或面向切面)的简称.,主要有Advice通知,Pointcut切点,Advisor通知器来完成AOP功能。组合起来说就是通过Advisor,可以定义应该使用哪个通知并在哪个关注点使用它,也就是说通过Advisor,把Advice和Pointcut结合起来,这个结合为使用IoC容器配置AOP应用,或者说即开即用地使用AOP基础设施,提供了便利。

在Spring AOP的基本实现中,可以了解Spring如何得到AopProxy代理对象,以及如何利用AopProxy代理对象来对拦截器进行处理。Proxy代理对象的使用,在Spring AOP的实现过程中是非常重要的一个部分,Spring AOP充分利用了像Java的Proxy、反射技术以及第三方字节码技术实现CGLIB这些技术方案,通过这些技术,完成了AOP需要的AopProxy代理对象的生成。回顾通过ProxyFactoryBean实现AOP的整个过程,可以看到,在它的实现中,首先需要对目标对象以及拦截器进行正确配置,以便AopProxy代理对象顺利产生;这些配置既可以通过配置ProxyFactoryBean的属性来完成,也可以通过编程式地使用ProxyFactory来实现。这两种AOP的使用方式只是在表面配置的方式上不同,对于内在的AOP实现原理它们是一样的。在生成AopProxy代理对象的时候,Spring AOP设计了专门的AopProxyFactory作为AopProxy代理对象的生产工厂,由它来负责产生相应的AopProxy代理对象,在使用ProxyFactoryBean得到AopProxy代理对象的时候,默认使用的AopProxy代理对象的生产工厂是DefaultAopProxyFactory对象。这个对象是AopProxy生产过程中一个比较重要的类,它定义了AopProxy

代理对象的生成策略,从而决定使用哪种AopProxy代理对象的生成技术(是使用JDK的Proxy类还是使用CGLIB)来完成生产任务。而最终的AopProxy代理对象的产生,则是交给JdkDynamicAopProxy和Cglib2AopProxy这两个具体的工厂来完成,它们使用了不同的生产技术,前者使用的是JDK的Proxy技术(它使用InvocationHandler对象的invoke完成回调),后者使用的是CGLIB的技术。

在得到AopProxy代理对象后,在代理的接口方法被调用执行的时候,也就是当AopProxy暴露代理的方法被调用的时候,前面定义的Proxy机制就起作用了。当Proxy对象暴露的方法被调用时,并不是直接运行目标对象的调用方法,而是根据Proxy的定义,改变原有的目标对象方法调用的运行轨迹。这种改变体现在,首先会触发对这些方法调用进行拦截,这些拦截为对目标调用的功能增强提供了工作空间。拦截过程在JDK的Proxy代理对象中,是通过invoke方法来完成的,这个invoke方法是虚拟机触发的一个回调;而在CGLIB的Proxy代理对象中,拦截是由设置好的回调callback方

法来完成的。有了这些拦截器的拦截作用,才会有AOP切面增强大显身手的舞台。

具体来说,在ProxyFactoryBean的回调中,首先会根据配置来对拦截器是否与当前的调用方法相匹配进行判断。如果当前的调用方法与配置的拦截器相匹配,那么相应的拦截器就会开始发挥作用。这个过程是一个遍历的过程,它会遍历在Proxy代理对象中设置的拦截器链中的所有拦截器。经过这个过程后,在代理对象中定义好的拦截器链中的拦截器会被逐一调用,直到整个拦截器的

调用完成为止。在对拦截器的调用完成以后,才是对目标对象(target)的方法调用。这样,一个普通的Java对象的功能就得到了增强,这种增强和现有的目标对象的设计是正交解耦的,这也是AOP需要达到的一个目标。

在拦截器的调用过程中,实际上已经封装了Spring对AOP的实现,比如对各种通知器的增强织入功能。尽管在使用Spring AOP的时候,看到的是一些advice的使用,但实际上这些AOP应用中接触到的advice通知是不能直接对目标对象完成增强的。为了完成AOP应用需要的对目标对象的增强,Spring AOP做了许多工作,对应于每种advice通知,Spring设计了对应的AdviceAdapter通知适配器,这些通知适配器实现了advice通知对目标对象的不同增强方式。对于这些AdviceAdapter通知适配器,在AopProxy代理对象的回调方法中,需要有一个注册机制,它们才能发挥作用。完成这个注册过程之后,在拦截器链中运行的拦截器已经是经过这些AdviceAdapter适配过的拦截器了。有了这些拦截器,再去结合AopProxy代理对象的拦截回调机制,才能够让advice通知对目标对象的增强作用实实在在地发生。

分享到:

专栏

类型标签

网站访问总量