1.Spring整体架构
1)核心容器(Core Container)
- Core模块,主要包含了Spring框架基本的核心工具类,是其他组件的核心;
- Beans模块,主要包含了访问配置文件、创建和管理Bean以及进行IoC/DI操作;
- Context模块,构建于Core和Beans上,ApplicationContext接口是关键;
- Expression Language模块,提供语言表达式用于在运行时查询和操纵对象;
2)Data Access/Integration
- JDBC模块,包含了Spring对JDBC数据访问进行封装的所有类;
- ORM模块,对象-关系映射API,利用ORM封装包,可以混合使用所有Spring提供的特性进行O/R映射;
- OXM模块,提供一个对 Object/XML 映射实现的抽象层;
- JMS模块,主要包含了一些制造和消费信息的特性;
- Transaction模块,支持编程和声明性的事务管理;
3)Web
Web上下文模块建立在应用程序上下文模块之上,为基于WEB的应用程序提供上下文
- Web模块,提供基础的面向WEB的集成特性
- Web-Servlet模块,包含了Spring的model-view-controller(MVC)实现
- Web-Struts模块,提供了对Struts的支持
- Web-Porlet模块,提供了用于Porlet环境和Web-Servlet模块的MVC实现
4)AOP
提供了一个符合AOP联盟标准的面向切面编程的实现,从而将逻辑代码分开,降低它们之间的耦合性。
5)Test
支持使用Junit和TestNG对Spring组件进行测试。
2.Bean的生命周期
BeanFactory中bean的生命周期
ApplicationContext中bean的生命周期
3.Bean的作用域
- 单例(Singleton):(默认)整个应用中,只创建bean的一个实例;
- 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean的实例;
- 会话(Session):在Web应用中,为每个会话创建一个bean实例;
- 请求(Request):在Web应用中,为每个请求创建一个bean实例;
4.资源访问(Resource)
资源抽象接口Resource在Spring框架中起着不可或缺的作用,Spring框架使用Resource装载各种资源,包括配置文件资源、国际化属性文件资源等。
- WritableResource:可写资源接口(Spring3.1增加接口),有两个实现类,即FileSystemResource和PathResource(Spring 4.0提供)
- ByteArrayResource:二进制数组表示的资源;
- ClassPathResource:类路径下的资源,资源以相对于类路径的方式表示;
- FileSystemResource:文件系统资源,以文件系统路径的方式表示;
- InputStreamResource:以输入流返回表示的资源;
- ServletContextResource:为访问Web容器上下文的资源设计的类,负责以相对于Web应用根目录的路径加载资源;
- UrlResource:封装了java.net.URL,使用户能够访问任何可以通过URL表示的资源;
- PathResource:Spring4.0提供的读取资源文件的新类,封装了java.net.URL、java.nio.file.Path、文件系统资源,使用户能够访问任何可以通过URL、Path、文件系统路径表示的资源;
为了访问不同类型的资源,Resource能够通过"classpath:"、"file"等资源地址前缀识别不同的资源类型,还支持Ant风格带通配符的资源地址。
资源配置地址前缀:
-
classpath:
从类路径加载资源,classpath:等价于classpath:/,资源文件可以在标准文件系统,也可以在JAR/ZIP的类包中,classpath*:
匹配所有JAR包及类路径; -
file:
使用UrlResource从文件系统目录中装载资源,可以使用绝对/相对路径; -
http://
使用UrlResource从web服务器中装载数据; -
ftp://
使用UrlResource从FTP服务器中装载数据; - 无前缀,根据ApplicationContext的具体实现类采用对应类型的Resource;
Ant风格通配符:
- ?:匹配文件名中的一个字符;
- *:匹配文件名中的任意字符;
- **:匹配多层路径;
资源加载器ResourceLoader接口仅有一个getResource(String location)方法,仅支持通过带资源类型前缀的资源地址加载文件资源;ResourcePatternResolver扩展了ResourceLoader,支持带资源类型前缀和Ant风格的资源路径表达式;PathMatchingResourcePatternResolver则是Spring标准实现类;
5.BeanFactory和ApplicationContext
1)BeanFactory
BeanFactory是一个类通用工厂,可以创建并管理各种类的对象,下图是BeanFactory的继承体系,主要是通过BeanFactory->HierarchicalBeanFactory->ConfigurableBeanFactory->...->XmlBeanFactory(废弃):
- ListableBeanFactory:接口定义了访问容器中Bean基本信息的若干方法,如bean的个数、获取某类bean的配置名、查看容器中是否包括某一个bean等
- HierarchicalBeanFactory:父子级联Ioc接口,子容器可以通过接口方法访问父容器;
- ConfigurableBeanFactory:增强了IoC容器的可定制性,定义了设置类装载器、属性编辑器、容器初始化后置处理器等方法;
- AutowireCapableBeanFactory:定义了将容器中的bean按某种规则进行自动装配的方法;
- SingletonBeanRegistry:定义了允许在运行期向容器注册单实例bean的方法;
- BeanDefinitionRegistry:每个由<bean>定义的bean通过BeanDefinition对象表示,描述了配置信息。BeanDefinitionRegistry接口提供了向容器手工注册BeanDefinition对象的方法;
通过资源加载器ResourceLoader加载Resource资源,在初始化BeanDefinitionReader时指定要用的BeanFactory,使用Resource加载BeanDefinition并启动IoC容器;然后就可以通过getBean方法获取bean,bean的初始化发生在第一次调用时。Spring在DefaultSingletonBeanRegistry提供了一个用于缓存单例bean的缓存器,基于HashMap,单例bean以beanName为键保存在这个HashMap中。
2)ApplicationContext
由BeanFactory派生而来,通过多个其他的接口扩展了BeanFactory的功能,提供了更多面向实际应用的功能,与BeanFactory不同的是,ApplicationContext在初始化应用上下文的时候就实例化所有单实例的bean。ApplicationContext的类继承体系如下:
- AppliactionEventPublisher:让容器拥有发布应用上下文事件的功能,比如容器开启/关闭事件,而实现了ApplicationListener事件监听接口的bean可以接受到容器事件并处理;
- MessageSource:为应用提供il8n国际化消息访问功能;
- ResourcePatternResolver:所有ApplicationContext实现类都实现了类似于PathMatchingResourcePatternResolver功能,可以通过带前缀的Ant风格的资源文件装载配置文件;
- LifeCycle:提供了start()和stop()两个方法,主要用于控制异步处理过程。ApplicationContext及其管理的bean同时实现该接口,ApplicationContext会将start/stop信息传递给容器中所有实现了该接口的bean,达到管理和控制JMX、任务调度等目的。
- ConfigurableApplicationContext扩展于ApplicationContext,它新增了两个主要方法close()和refresh(),让ApplicationContext具有启动、刷新和关闭应用上下文的能力。
3)WebApplicationContext
专门为Web应用准备的,允许从相对于Web根目录的路径中装载配置文件完成初始化工作,在非Web应用环境下,bean只有singleton和prototype两种作用域,WebApplicationContext添加了3个新的作用域:request、session和global session;
WebApplicationContext扩展了ApplicationContext,定义了一个常量ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE存放WebApplicationContext实例;
ConfigurableWebApplication扩展了WebApplicationContext,允许通过配置的方式实例化WebApplicationContext:
-
setServletContext(ServletContext servletContext)
:为Spring设置web应用上下文; -
setConfigLocations(String[] configLocations)
:设置Spring配置文件路径,一般是相对于Web根目录地址;
6.IOC
IoC(控制反转)也被称为DI(依赖注入),让调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。
IoC主要可以划分为3种类型:构造函数注入、属性注入和接口注入。Spring支持构造函数注入和属性注入。
- 构造函数注入:通过调用类的构造函数,将接口实现类通过构造函数变量传入;
- 属性注入:有选择地通过set方法完成调用类所需依赖的注入;
- 接口注入:将调用类所有依赖注入的方法抽象到一个接口中,调用类通过实现该接口提供相应的注入方法;
IOC容器的初始化过程:
IoC容器的初始化由refresh()方法启动,包括BeanDefinition的Resource定位、载入和注册三个基本过程,这里仅仅是IoC容器的初始化过程,不包含依赖注入过程,
- Resource定位,指BeanDefinition的资源定位(ClassPath、FileSystem、Url),Resource封装了对配置文件的I/O操作;
- BeanDefinition的载入,把用户定义好的bean表示成IoC容器内部的数据结构,即
BeanDefinition
。通过调用XML解析器得到document对象,并在documentReader(DefaultBeanDefinitionDocumentReader
)里对Spring的bean规则进行解析,通过BeanDefinitionParserDelegate
(定义了对各种Spring Bean定义规则的处理)进行Document文档树解析得到BeanDefinitionHolder
(封装了BeanDefinition、 Bean的名字和别名) - 向IoC容器注册BeanDefinition,通过调用BeanDefinitionRegistry接口的实现来完成,IoC容器将BeanDefinition注入到HashMap中,以持有这些数据,key为bean的名字,value为beanDefinition;
IOC的依赖注入
初始化过程主要是在IoC容器中建立BeanDefinition数据映射,并没有对Bean的依赖关系进行注入,依赖注入的过程是在用户第一次向IoC容器索要bean时触发的,当然也可以通过lazy-init属性让容器完成对bean的预实例化。
- 在
AbstractBeanFactory
中实现了getBean(),触发了依赖注入,并尝试从当前工厂获取单例模式下的bean; - 若当前工厂不存在该bean的单例实例时,递归的调用父类工厂的getBean()方法,直到存在此单例bean或对应的BeanDefinition;
- 获取该BeanDefinition,并递归的获取当前的bean的所有依赖(实际是通过依赖bean的名,递归的调用
getBean(dependsOnBean)
对其进行注册,即经历了调用getBean()后当前bean经历的所有流程),并注册为依赖bean; - 判断该bean是Singleton、Prototype还是作用域型,并调用相应的
creatBean()
方法进行bean的创建; - 在creantBean()中,若该bean实现了PostProcessor,则返回一个代理,否则调用
doCreatBean()
对bean进行创建; -
doCreatBean()
中主要是调用creatBeanInstance()
完成bean的实例化。实例化方法主要有:工厂方法实例化或依据配置通过CGLib进行构造器实例化。在IoC容器中,初始化策略由SimpleInstantiationStrategy
类提供2种方法,一种是通过BeanUtils,使用了JVM的反射功能;一种就是通过CGLIB; - bean实例化后,通过调用
populateBean()
进行依赖注入流程;依赖关系定义在解析得到的BeanDefinition中; - 如果bean实现了
InstantiationAwareBeanPostProcessor
,则调用postProcessorAfterInstantiation()
方法对bean进行初始化后的处理; - 进行依赖注入的准备,先处理autowire的注入,再调用
applePropertyValues()
进行属性注入,在属性注入之前,如果bean实现了InstantiationAwareBeanPostProcessor
,则调用其postProcessProperty()
方法对bean进行注入前的相应处理; - 调用
BeanDefinitionValueResolver
对BeanDefinition进行参数解析。如果是一个引用,且对应bean在双亲IoC容器中,则到双亲IoC容器中去获取(通过getBean(),若依赖bean没有实例化和依赖注入,则触发),否则在当前IoC容器种获取bean; - 调用
BeanDefinitionValueResolver
中的resolveValueIfNecessary()
方法对获取得到的bean实例进行类型处理; - 解析完成后,已经为依赖注入准备好了所需条件,通过调用
BeanWrapper
中的setPropertyValues()
进行真正的依赖注入,对不同类型的参数(Array、List、Map等),通过反射机制取得注入属性的set方法,将对象注入进去;
Bean的初始化方法
- 在调用bean的初始化方法之前,会调用一系列的aware接口实现(如果bean实现了该接口),把相关的BeanName、BeanClassLoader以及BeanFactory注入到bean中。如果实现了BeanPostProcessor,则调用
postProcessorBeforeInitialization()
。如果实现了InitializingBean接口,则调用相应的afterPropertiesSet()
方法实现初始化处理; - 如果Bean配置了initMethod,则通过
invokeCustomInitMethod()
来调用,实际上是根据初始化方法名,然后通过反射机制调用; - 最后如果实现了BeanPostProcessor接口,则调用
postProcessorAfterInitialization
方法;
Bean的依赖检查
在一般情况下,Bean的依赖注入是在应用第一次向容器索取Bean的时候发生,不能保证注入一定能够成功,如果需要重新检查这些依赖关系的有效性,IoC容器中,设计了一个依赖检查特性,在Bean定义中设置dependency-check
属性来指导依赖检查模式。具体实现是在AbstractAutowireCapableBeanFactory
实现creatBean的过程中完成的,会对Bean的Dependencies属性进行检查,如果不满足要求则抛出异常。
7.AOP
通过在代理类中包裹切面,核心是JDK动态代理,以动态代理为基础,设计出一系列的AOP的横切实现,比如前置通知、返回通知、异常通知等。Spring AOP需要为目标对象建立代理对象,可以通过JDK的Proxy或者第三方类生成器CGLIB来完成,然后启动代理对象的拦截器来完成各种横切面的织入。Spring在运行时通知对象,只支持方法连接点,不支持字段和构造器连接点。
AOP基本术语:
- 通知:定义了切面是什么以及何时使用,描述了切面要完成的工作以及何时执行此工作。切面可以应用5中类型的通知:前置通知、后置通知、返回通知、异常通知、环绕通知;
- 连接点:在应用执行过程中能够插入切面的一个点,可以是调用方法时、抛出异常时、修改一个字段时;
- 切点:匹配通知所要织入的一个或多个连接点,即定义了在何处进行通知操作;
- 切面:通知与切点的结合,定义了在何时何处完成何种功能;
- 引入:运行我们向现有类添加新的方法或属性;
- 织入:将切面应用到目标对象并创建新的代理对象的过程,即切面在指定的连接点上被织入到目标对象中。可以在编译期、类加载期或在运行期通过动态代理织入;
建立AopProxy代理对象
在Spring的AOP模块中,主要的部分是代理对象的生成,通过调用和配置Spring的ProxyFactoryBean
来完成,该类封装了主要代理对象的生成过程,可以使用JDK的Proxy和CGLIB两种生成方式。
- 定义使用的通知器Advisor,以Bean的形式。通知器的实现定义了需要对目标对象进行增强的切面行为;
- 定义ProxyFactoryBean,封装AOP功能的主要类,需要设定与AOP实现相关的重要属性,
proxyInterface
,interceptorNames
和target
; - 定义target属性,作为target属性注入的Bean,是需要AOP通知器中的切面应用来增强的对象;
1)ProxyFactoryBean生成AopProxy代理对象
以getObject()
方法作为入口,需要对target目标对象增加的增强处理,都通过getObject方法实现了封装。首先对通知器链进行初始化,通知器链封装了一系列的拦截器,在生成代理对象时,根据singleton类型和prototype类型的不同,还需要对代理对象的生成做一个区分;
-
通知器链初始化:通知器链是在
initializeAdvisorChain()
中完成,有标志位advisorChainInitialized
,表示通知器链已经初始化,也就是说通知器链只会在第一次通过ProxyFactoryBean去获取代理对象的时候初始化。- 通知器链的初始化会读取配置中的所有通知器,区分了全局通知器Global(代理工厂必须继承了ListableBeanFacoty)和局部的singleton、prototype类型;
- 接着会将得到的通知器名字交给
addAdvisorOnChainCreation()
方法,调用IoC容器的getBean()
方法获取到通知器Bean加入到通知器链中;
-
生成singleton代理对象:由
getSingletonInstance()
方法实现,是生成AopProxy代理对象的入口,代理对象会封装对目标对象target的调用,针对target对象的方法调用行为会被这里生成的代理对象所拦截。- 首先读取ProxyFactoryBean中的配置,为生成代理对象做准备,比如判断目标类是否存在,设置代理对象的接口等;
- 接着,使用AopProxyFactory创建AopProxy,使用的是
DefaultAopProxyFactory
,其作为生成AopProxy对象的生成工厂,如果目标对象是接口类的实现,则适合使用JdkDynamicAopProxy
来生成代理对象,否则Spring会使用Cglib2AopProxy
来生成目标对象的代理对象;
AopProxy的接口设计很简单,就是获取Proxy代理对象,在此接口下设计了Cglib2AopProxy
和JdkDynamicAopProxy
两种代理对象的实现,获取Proxy代理对象的方式有两种,一种需要指定ClassLoader,另一种不需要。
-
在
JdkDynamicAopProxy
中生成代理对象,使用JDK的Proxy类来生成代理对象。- 首先从advised对象中取得代理对象的代理接口配置; - 然后调用Proxy类的`newProxyInstance()`方法,得到对应的Proxy代理对象。
在生成代理对象时,需要三个参数,类装载器、代理接口、Proxy回调方法所在的对象,该对象需要实现InvocationHandler
接口,而JdkDynamicAopProxy
实现了InvocationHandler
接口及其invoke()
回调方法,所以可以直接使用。
- 在
Cglib2AopProxy
中生成代理对象- 仍是首先从advised对象中取得在IOC容器中配置的target代理对象;
- 然后获取代理对象target的代理接口,创建并配置CGLIB的Enhancer,Enhancer对象就是CGLIB的主要操作类;
- 设置Enhancer对象,包括设置代理接口,回调方法callback,其回调是由
DynamicAdvisedInterceptor
来完成,此对象的回调入口是intercept()
方法; - 最后通过Enhancer生成代理对象。
2)Spring AOP拦截器调用的实现
Spring AOP通过上述两种不同的代理对象生成方法生成代理对象时,相关的拦截器已经配置到了代理对象中,拦截器在代理对象中起作用是通过对这些方法的回调来完成的。
-
JdkDynamicAopProxy的invoke拦截:当Proxy对象的代理方法被调用时,
JdkDynamicAopProxy
(实现了InvocationHandler接口)的invoke方法作为Proxy对象的回调函数被触发,从而通过invoke的具体实现,来完成对目标对象方法调用的拦截或者说功能增强。在invoke方法中完成了对Proxy对象的代理设置,包括获取目标对象、拦截器链,如果没有设置拦截器,则直接调用target的相应方法;如果设置了拦截器,需先调用拦截器,通过生成ReflectiveMethodInvocation
对象来完成对AOP功能实现的封装; - Cglib2AopProxy的intercept拦截:其回调是在DynamicAdvisedInterceptor对象中的intercept方法实现的,与JdkDynamicAopProxy的回调实现非常相似,只是在Cglib2AopProxy的intercept通过构造CglibMethodInvocation对象来完成拦截器链的调用;
目标对象方法的调用:若未设置拦截器,则直接调用目标对象的方法,对于JdkDynamicAopProxy,通过AopUtils
使用反射机制在invokeJoinpointUsingReflection()
方法中实现,首先得到调用方法的反射对象,然后使用invoke启动对方法反射对象的调用;对于Cglib2AopProxy,通过其MethodProxy对象来直接完成。
AOP拦截器链的调用:虽然可以使用JDK和CGLIB生成不同的AopProxy代理对象,从而构造不同的回调方法来启动对拦截器链的调用,但对拦截器的调用都是在ReflectiveMethodInvocation
中,通过proceed()
方法逐个运行拦截器的拦截方法来实现的。不过在运行拦截器的拦截方法之前,会通过对代理方法完成一个匹配判断来决定拦截器是否满足切面增强的要求,即在切点中进行matches的匹配过程,如果满足则从拦截器中获取通知器,并启动拦截器的invoke方法进行切面增强;如果不匹配则递归的调用proceed方法,直到所有拦截器都被运行过,然后直接调用目标对象的实现方法。
配置通知器:拦截器的配置是在代理对象的回调方法中,由advised对象(AdvisedSupport对象)完成的,通过调用其getInterceptorsAndDynamicInterceptionAdvice
方法生成拦截器,并且为了提高效率还加入了缓存,即针对目标对象方法的拦截器链在生成后会被缓存,所以只需生成一次。在此方法中生成拦截器链的工作是由advisorChainFactory
(DefaultAdvisorChainFactory对象)完成的,此对象实现了拦截器链的获取,具体流程如下:
- 首先设置一个List,长度由配置的通知器个数决定;
- 然后,
DefaultAdvisorChainFacoty
会通过AdvisorAdapterRegistry
来实现拦截器的注册; - 利用AdvisorAdapterRegistry注册器来对从ProxyFactoryBean配置中获取的通知进行适配(判断目标类是否与通知器适配),从而获得相应的拦截器,并加入到前面设置好的List中;
拦截器适配和注册完成后,List中的拦截器会被JDK/CGLIB生成的代理对象的回调方法invoke/intercept取得,并启动拦截器的invoke调用,最终触发通知的切面增强;
Advice通知的实现
在为AopProxy代理对象配置拦截器的实现中,有一个取得拦截器的配置过程,由DefaultAdvisorChainFactory
实现,这个工厂类负责生成拦截器链,在它的getInterceptorsAndDynamicInterceptionAdvice
方法中,有一个适配和注册的过程,通过配置Spring预先设计好的拦截器,Spring加入了它对AOP实现的处理。
- 首先,在
DefaultAdvisorChainFactory
实现中,构造了一个GlobalAdvisorAdapterRegistry的单例; - 然后,对配置的Advisor通知器进行逐个遍历,这些通知器链都是配置在
interceptorNames
中的; - 从
getInterceptorsAndDynamicInterceptionAdvice
传递进来的advised参数对象中,可以方便地获取到配置的通知器,然后由GlobalAdvisorAdapterRegistry提供的DefaultAdvisorApapterRegistry
单例来完成拦截器的适配和注册过程; - DefaultAdvisorApapterRegistry中,首先其具有一系列与advice通知相对应的adapter适配实现,通过调用adapter的support方法,来判断取得的advice属于什么类型的advice通知,从而根据不同的通知来注册不同的
AdviceInterceptor
。而这些AdviceInterceptor早已设计好,为实现不同的advice功能提供服务;
以MethodBeforeAdviceAdapter
为例,其实现了supportAdvice方法,advice是否是MethodBeforeAdvice的实例;实现了getInterceptor方法,将advice通知对象封装到MethodBeforeAdviceInterceptor
对象中。由于切面增强是通过遍历拦截器,调用其invoke方法完成,- 在MethodBeforeAdviceInterceptor中,invoke方法被触发,会先调用advice的before方法,达到对目标的增强效果(在方法调用前完成通知),然后调用MethodInvocation的proceed方法实现目标对象相应方法的调用;
AspectJ的切点表达式
AspectJ指示器 | 描述 |
---|---|
arg() |
限制连接点匹配参数为知道类型的执行方法 |
@args() |
限制连接点匹配参数由指定注解标注的执行方法 |
executuon() |
用于匹配是连接点的执行方法 |
this() |
限制连接点匹配AOP代理的bean引用为指定类型的类 |
target |
限制连接点匹配目标对象为指定类型的类 |
@target() |
限制连接点匹配特定的执行对象,这些对象对应的类具有指定类型的注解 |
within() |
限制连接点匹配指定的类型 |
@within() |
限制连接点匹配指定注解所标注的类型 |
@annotation |
限制匹配带有指定注解的连接点 |
8.Spring MVC
在自动装配中,不需要对Bean属性做显式的依赖关系声明,只需要配好autowiring属性,IoC容器会根据这个属性的配置,使用反射自动查找属性的类型或者名字,然后基于属性的类型或名字来自动匹配IoC容器中的Bean,从而自动地完成依赖注入。
请求处理流程
- 整个过程始于客户端发出一个HTTP请求,Web应用服务器接受到这个请求。如果匹配DispatcherServlet的请求映射路径(web.xml中指定),则Web容器将该请求转交给DispatcherServlet;
- DispatcherServlet接收到这个请求后,将根据请求信息(包括URL,HTTP方法,请求报头,请求参数,Cookie等)及HandlerMapping的配置找到处理请求的处理器(Handler);
- 当DispatcherServlet根据HandlerMapping得到对应请求的Handler后,通过HandlerAdapter对Handler进行封装,再以统一的适配器接口调用Handler;
- 处理器完成业务逻辑的处理后将返回一个ModelAndView给DispatcherServlet,ModelAndView包含了视图逻辑名和模型数据信息;
- ModelAndView中包含的是"逻辑视图名"而非真实的视图对象,DispatcherServlet借由ViewResolver完成逻辑视图名到真实视图对象的解析工作;
- 当得到真实的视图对象View后,DispatcherServlet就使用这个View对象对ModelAndView中的模型数据进行视图渲染;
- 最终客户端得到的响应信息可能是HTML页面、XML或JSON、甚至是一张图片或一个PDF文档等不同的媒体形式;
8.1 上下文在Web容器中的启动
IoC容器启动的基本过程
IOC容器的启动过程就是建立上下文的过程,是与ServletContext相伴而生的,同时也是IoC容器在Web应用环境中的具体表现之一。由ContextLoaderListener启动的上下文为根上下文,还有一个与Web MVC相关的上下文用来保存控制器需要的MVC对象。
在web.xml中配置的ContextLoaderListener
,为在Web容器中建立IoC容器服务,其实现了ServletContextListener
接口,提供了Servlet生命周期结合的回调,比如contextInitialized
和contextDestroyed
方法,在contextInitialized的实现方法中完成了WebApplicationContext的建立,具体的载入过程是由ContextLoaderListener交由ContextLoader
来完成的,主要是完成在Web容器中建立起双亲IoC容器,以及生成相应的WebApplicationContext并将其初始化。
Web容器中的上下文设计
在WebApplicationContext中,可以看到ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
常量,用来索引在ServletContext中存储的根上下文,同时提供了getServletContext()
方法来获取当前Web容器的Servlet上下文环境。
在启动过程中,Spring会使用一个默认的WebApplicationContext实现作为IoC容器,即XmlWebApplicationContext
,在其初始化过程中,Web容器中的IoC容器被建立起来,该过程与对IoC容器的初始化分析一样,有loadBeanDefinition对BeanDefinition的载入,不过在Web环境中,对BeanDefinition的Resource有特别要求,默认的BeanDefinition的配置路径为/WEB-INF/applicationContext.xml
,以一个常量保存在XmlWebApplicationContext中。
ContextLoader的设计与实现
Web应用程序启动时载入IoC容器的功能是由配置的监听器ContextLoaderListener完成,通过使用ContextLoader完成IoC容器,即WebApplicationContext的初始化。
- 首先,从Servlet事件中得到ServletContext;
- 然后,读取配置在web.xml的各个相关属性值;
- 接着,ContextLoader会实例化WebApplicationContext,并完成其载入和初始化过程;
此监听器是启动根IoC容器并把它载入到Web容器的地方,生成的根上下文被绑定到Web应用程序的ServletContext上,需要访问根上下文可以从WebApplicationContextUtils
类的静态方法getWebApplicationContext
中得到。
ContextLoaderListener实现了ServletContextListener
接口,该接口里的函数会结合Web容器的生命周期被调用,它会监听ServletContext事件并作出响应。在服务器启动时,ServletContextListener的contextInitialized被调用;在服务器将要关闭时,contextDestroyed方法被调用。
在初始化回调中,创建了ContextLoader,并利用它来完成IoC容器的初始化。此根上下文会作为Web容器的唯一实例而存在,如果在初始化过程中,发现已经有根上下文被创建,则抛出异常提示创建失败。根上下文的创建需要设置双亲上下文、ServletContext
- 根据传入的ServletContext决定使用什么样的类在WEB容器中作为IoC容器,可以通过设置
CONTEXT_CLASS_PARAM
参数来指定,若未指定则使用默认的XmlWebApplicationContext; - 然后直接实例化IoC容器并设置它的双亲上下文、ServletContext以及配置文件的位置参数;
- 最后,调用refresh方法启动容器的初始化,也就是一般IoC容器的初始化过程;
8.2 Spring MVC的设计与实现
在完成对ContextLoaderListener的初始化之后,Web容器开始初始化DispatcherServlet
的过程,DispatcherServlet会建立自己的上下文来持有Spring MVC的Bean对象,在建立此IoC容器时,会从ServletContext中得到根上下文作为其双亲上下文。
DispatcherServlet通过集成FrameworkServlet
和HttpServletBean
而继承了HttpServlet
,通过使用Servlet API来对HTTP请求进行响应,成为Spring MVC的前端处理器,同时成为MVC模块与Web容器集成的处理前端,其工作大致可以分为两个部分:
- 初始化部分,由HttpServletBean的
init
方法对initServletBean()
的调用来启动,通过initWebApplicationContext()
方法建立IoC容器,最终调用DispatcherServlet的initStrategies
方法,在此方法中,DispatcherServlet对MVC模块的其他部分进行了初始化,如handlerMapping、ViewResolver等; - 对HTTP请求进行响应,作为一个Servlet,Web容器回调用Servlet的
doGet()
和doPost()
方法,经过FrameworkServlet的processRequest()
简单处理后,会调用DispatcherServlet的doService()
方法,在此方法中封装的doDispatch()
实现了MVC模式的主要功能;
DispatcherServlet的启动和初始化
- 在init方法中开始初始化,读取配置在ServletContext中的Bean属性参数,这些属性参数设置在web.xml的Web容器初始化参数中,如PropertyValues和BeanWrapper;
- 接着在initWebApplicationContext方法中,执行DispatcherServlet持有的IoC容器的初始化过程,建立一个新的上下文,并将其设置为根上下文的子上下文,这样根上下文就可以被各个Servlet持有的上下文所共享(通过getBean向IoC容器获取Bean时,会先到其双亲IoC容器中获取);
- 建立DispatchServlet的上下文,需要把根上下文作为参数传递给它;
- 然后使用反射技术实例化上下文对象,根据默认配置,该上下文对象也是XmlWebApplicationContext对象
- 实例化结束后,需要为此上下文设置基本配置,如其双亲上下文、ServletContext引用以及Bean定义配置的文件位置等;
- 最后调用refresh方法初始化该容器;
- 通过initWebApplicationContext方法中调用的
onRefresh()
方法启动DispatchServlet中的initStrategies
方法,来完成整个Spring MVC框架的初始化,包括MVC框架的实现元素,比如支持国际化的LocalResolver
、支持request映射的HandlerMappings
以及视图生成的ViewResolver
等的初始化。以HandlerMappings为例:- 根据
detectAllHandlerMapping
设定值,默认为true,即默认从所有IoC容器中获取控制器,否则从当前IoC容器中获取; - 若都获取不到,则需要设定默认控制器,此默认值设置在
DispatcherServlet.properties
中;
- 根据
- 初始化的最后,把上述生成的IoC容器设置到Web容器的上下文中;
MVC处理HTTP分发请求
①HandlerMapping的配置与设计
在初始化完成时,在上下文环境中已定义的所有HandlerMapping
都已经被加载了,加载的handlerMappings被放在一个List中并被排序,存储着HTTP请求对应的映射数据。List中每一个元素对应一个具体的handlerMapping的配置,一般每个handlerMapping可以持有一系列从URL请求到Controller的映射(使用Map)。通过这些HandlerMapping中定义的映射关系,即URL请求与控制器的对应关系,使Spring MVC应用可以根据HTTP请求确定对应的Controller。
映射关系是通过接口类HandlerMapping封装的,在HandlerMapping接口中定义getHandler()
方法,通过此方法,可以获得与HTTP请求对应的HandlerExecutionChain,其封装了具体的Controller和Interceptor链,通过拦截器给handler对象提供功能增强,同时提供了拦截器链的维护,可以调用addInterceptor()
方法为拦截器链增加拦截器。
例如具体的SimpleUrlHandlerMapping
为例,其根据URL映射的方式,注册Handler和Interceptor,从而维护一个反应这种映射关系的handlerMap。注册过程在容器对Bean进行依赖注入时发生,实际上是通过Bean的postProcessor来完成。
主要是通过其基类AbstractUrlHandlerMapping
定义的registerHandler(String urlPath,Object handler)
方法,在处理过程中:
- 如果使用Bean的名称作为映射,则直接从容器中获取此HTTP映射对应的Bean;
- 否则,对不同的URL配置进行解析,如HTTP请求中配置成
/
(映射的controller设置为rootHandler)和通配符/*
(映射的controller设置为defaultHandler)的URL以及正常URL,
②使用HandlerMapping完成请求的映射处理
在AbstractHandlerMapping中启动getHandler的调用,取得Handler的具体过程在getHandlerInternal(HttpServletRequest request)
方法中实现,实现过程包括了从HTTP请求中得到URL,并根据URL到urlMapping中获得handler。
③Spring MVC对HTTP请求的分发处理
在DispatcherServlet中,对HTTP请求的处理是在doService方法中完成的,实际是其交由doDispatch来完成,包括准备ModelAndView,调用getHandler来响应HTTP请求,然后通过执行Handler的处理来得到返回的ModelAndView结果,最后把该对象交给相应的视图对象去呈现。
- 首先,根据请求调用getHandler方法得到handler;
- 在获取handler时,首先会在HttpRequest中取得缓存的handler,对应HTTP的
HANDLER_EXECUTION_CHAIN_ATTRIBUTE
属性位置; - 如果获取不到(未缓存),通过在DispatcherServlet中持有的handlerMapping来生成一个(对应上述②);
- 遍历所有的handlerMapping,直到找到一个需要的handler为止;
- 最后返回的是一个
HandlerExecutionChain
对象,并在HttpRequest进行缓存;
- 在获取handler时,首先会在HttpRequest中取得缓存的handler,对应HTTP的
- 调用handler的拦截器,进行预处理;
- 在执行handler之前,获取响应的适配器
HandlerAdapter
,并检查handler的合法性,即判断此handler是否为Controller接口的实现类; - 通过调用HandlerAdapter的
handle
方法,实际触发对Controller的handleRequest方法的调用进行请求处理,处理的结果封装到ModelAndView对象中,为视图提供展现数据; - 判断视图是否需要进行视图名的翻译和转换,并调用拦截器进行后置处理;
- 调用
render()
方法使用视图对ModelAndView数据进行展现;