《深入理解Spring MVC源代码》阅读笔记
2021-02-27
控制器(Controller)
- 概念:控制器层用于接收用户的请求,根据用户请求调用不同的服务,并将响应结果返回到视图。
- 功能:处理器查找->处理器执行->视图查找->模型绑定->返回响应。
处理器查找
- 直接URL映射:向Spring注册
SimpleUrlHandlerMapping
类型的Bean。
- BeanName与URL映射:如果Bean的名称或别名以
/
开头,则该Bean会自动映射为处理器,URL为Bean的名称。注册逻辑在BeanNameUrlHandlerMapping
中实现。
@RequestMapping
映射:为方法添加@RequestMapping
注解,则该方法会自动映射为处理器方法,URL在注解中指定。注册逻辑在RequestMappingHandlerMapping
中实现。
拦截器
- 在处理器查找时,
HandlerMapping
接口实现类并不直接返回处理器,而是返回HandlerExecutionChain
类,该类封装了所有应用在该处理器上的拦截器,拦截器的类型是HandlerInterceptor
接口的实现类。
- 拦截器中有三个方法:
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
(预处理)、void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView)
(结果处理)、void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex)
(异常处理)。
- 在执行处理器前,会先执行
HandlerInterceptor
接口实现类的preHandle()
,若方法返回false
则中断处理流程,若方法返回true
则继续后续的拦截器处理。
- 若处理器正常执行完成则会先执行
HandlerInterceptor
接口实现类的postHandle()
,所有postHandler()
执行完成后再执行所有的afterCompletion()
。
- 若处理器执行错误则会执行
HandlerInterceptor
接口实现类的afterCompletion()
。

适配器
- 在拦截器的
preHandler()
均返回true
后,会把当前的处理器交给可以执行它的适配器HandlerAdapter
接口实现类去执行。
HandlerMethod
类对应的适配器为RequestMappingHandlerAdapter
。
处理器执行
HttpRequestHandler
接口:只包含一个public void handleRequest(HttpServletRequest request, HttpServletResponse response)
方法,适合处理简单的HTTP请求。
Controller
接口:只包含一个public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
方法,返回值中提供了对视图和模型的绑定功能。
HandlerMethod
类:有@RequestMapping
注解标记的方法会封装为该类,类中包含该方法所在的Bean及该方法的引用。
视图查找和模型绑定
- 控制器最终对视图进行渲染时,会从
ModelAndView
类型返回值中获取视图或根据视图名查找视图,之后再从ModelAndView
类型返回值中获得模型数据,通过调用视图的渲染方法传入模型数据执行渲染操作,渲染后把结果通过HTTP Response返回给用户。
处理器方法参数解析
- Spring MVC为
@RequestMapping
标记的处理器方法增加了参数自动解析的功能。在适配器执行处理器方法前,会遍历当前处理器方法中所有的参数签名,根据每个参数签名自动从请求信息或当前应用信息中心获取最合适的值,作为执行处理器方法的参数列表,在执行时传递给该处理器方法。
MethodParameter
类:是Spring MVC对处理器方法中的参数封装,包含了参数在参数列表中的索引、所在方法、参数注解、当前参数名等参数相关的元信息。
HandlerMethodArgumentResolver
接口:
- 声明
boolean supportsParameter(MethodParameter parameter)
方法,该方法根据参数元信息判断是否可以直接解析该参数的参数值。
- 声明
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory)
方法,该方法需要传入请求信息与参数信息,返回Object
类型的值,该值即解析后当前处理中参数对应的参数值。
RequestMappingHandlerAdapter
类中封装了HandlerMethodArgumentResolver
接口实现类的列表,参数解析时遍历参数列表,对每个参数遍历解析器列表,当supportsParameter()
方法返回true
时,表示当前解析器支持解析此参数,再调用该解析器的resolveArgument()
方法,最终获得参数值数组。
- 根据参数信息对参数进行解析的方式大致分为两类:根据参数类型解析、根据参数注解解析。
- 参数类型解析策略:
WebRequest
、NativWebRequest
:Spring MVC对Servlet原生Request的封装。
ServletRequest
、ServletResponse
及其子类:Servlet API中对HTTP请求和响应的封装。
HttpSession
:HTTP会话。
HttpMethod
:当前的请求方法。
HttpEntity<T>
:Spring MVC封装的HTTP请求实体,可以用于访问请求头和请求体,请求体用HttpMessageConverter
的实现类转换为T
类型的对象。
- 参数注解解析策略:
@PathVariable
:用于获取@RequestMappinig
的path
中的模板变量
@MatrixVariable
:用于获取URL中路径段用分号隔开的键值对的值。
@RequestParam
:用于绑定请求参数,使用Request.getParameter()
获取值。
@RequestHeader
:用于绑定请求头参数,使用Request.getHeader()
获取值。
@CookieValue
:用于绑定Cookie值,使用Request.getCookies()
获取值。
@RequestBody
:用于绑定请求体数据,使用HttpMessageConverter
接口实现类把请求的InputStream
数据流转化为声明类型。
@RequestPart
:用于获取multipart/form-data类型请求中的请求块。
@ModelAttribute
:用于绑定当前数据模型中的属性值。
@SessionAttribute
:用于从Session的属性中获取值。
@RequestAttribute
:用于从Request的属性中获取值。
返回值处理
- 对返回值的支持大致分为三类:数据模型相关类型、视图相关类型、
@ResponseBody
相关类型。举例如下:
@ModelAttibute
:返回数据为模型属性,是默认类型。
@ResponseBody
:返回值直接作为相应内容。
HttpHeaders
:只返回响应的头数据,不返回响应体。
HttpEntity
、ResponseEntity
:结合HttpHeaders
与@ResponseBody
的功能。
控制器增强
- 数据绑定:有注解
@InitBinder
的方法(返回值void)可以为当前控制器的WebDataBinder
添加新的类型转换器。(所有需进行类型转换的参数绑定,都需要用到WebDataBinder
的数据转换功能)
- 模型属性:有注解
@ModelAttribute
的非处理器方法可以为当前控制器的所有处理器方法附加模型属性。(在执行处理器方法前先执行@ModelAttribute
标记的方法,并添加该方法的返回值得到模型中)
- 异常处理:有注解
@ExceptionHandler
的方法可以为当前控制器的所有处理器方法声明异常处理方法。
- 请求体与响应体增强:在
@RequestBody
、@ResponseBody
的处理中增强消息转换功能,可以在读请求体前、读请求体后、写响应体前对数据做一些特殊处理。
@ControllerAdvice
注解:控制器增强器,可以为其中的方法标记@InitBinder
、@ModelAttribute
、@ExceptionHandler
三种增强器注解。
- 三种注解如果在
@ControllerAdvice
标记的Bean中定义,此时增强功能按照@ControllerAdvice
注解定义的条件范围应用到处理器方法中;如果在控制器Bean中定义,此时增强功能只应用于当前控制器中。
扩展支持
跨域请求
- CORS跨域资源共享:分为简单请求和非简单请求。对于简单请求,浏览器为请求头添加Origin字段,标记当前发出请求的页面所在域,服务端检测请求头中的Origin字段,如果是可信的请求源,则可以在该请求的响应头中添加Access-Control-Allow-Origin字段,内容为请求的Origin,表明允许当前Origin进行跨域请求;对于非简单请求,会在正式通信前增加一次HTTP OPTIONS请求(称为预检请求),服务器根据预检请求里的信息判断是否允许该CORS请求,通过后才允许发送正式请求。
- 简单请求/非简单请求:请求方法是HEAD/GET/POST,HTTP header字段不超出Accept/Accept-Language/Content-Language/Last-Event-ID/Content-Type的请求是简单请求,否则为非简单请求。
- 有
@CrossOrigin
注解的处理器方法可允许其origin
字段发起的跨域请求。
WebSocket
- WebSocket协议:基于TCP协议,可以使客户端与服务端创建一个持久的Socket连接,在不关闭该连接的情况下,双方可以互相主动发送消息。
- 客户端首先使用HTTP向WebSocket的连接地址发起一个GET请求,请求头中的Upgrade代表请求协议升级为WebSocket协议,Connection使用Upgrade标记。
- 服务端回复响应状态码为101表示切换协议,Upgrade和Connection与请求头相同。
- 当前两步(握手请求)完成后,浏览器会使用与握手请求相同的TCP连接发起WebSocket连接请求。
- 连接请求成功时,才算完成WebSocket的连接,可以进行后续的发送与接收消息动作。
- 定义一个继承自
WebSocketHandler
类的处理器类,包含afterConnectionEstablished()
(连接开启时的处理方法)、handleMessage()
(收到消息时的处理方法)、afterConnectioinClosed()
(关闭连接时的处理方法)、handleTransportError()
(传输错误时的处理方法)等几个方法。在@Configuration
注解类上添加开启WebSocket的注解@EnableWebSocket
,该类实现WebSocketConfigurer
接口,该类中的registerWebSocketHandlers()
方法对处理器类进行注册。
- WebSocket中的Session与HTTP中的Session不是同一个,它直接与连接绑定。如果想要通过WebSocket获取HTTP的Session信息,可以在握手时通过HTTP的Session获取信息放入WebSocket的Session。
- 通过WebSocket协议可以传输基于STOMP协议的消息负载,它规定了各种头信息用于表示此条消息的含义。STOMP协议整体框架是消息发布订阅协议。
异步请求
- Servlet对异步请求的支持:通过
ServletRequest
的startAsync()
方法标记对此请求开启异步处理,该方法返回一个AsyncContext
,其中包含有Request
与Response
,可以对此AsyncContext
进行异步操作,在其他线程中直接对其中的Request
和Response
进行读写都可行,在处理完成后,调用其complete
表示处理完成。
- SpringMVC对异步请求的支持:
Callable<T>
:返回值为T
类型,与Runnable
接口效果类似。
WebAsyncTask
:封装了Callable
类型的实例作为异步执行逻辑,同时还可定制异步处理的超时、异步执行器、超时回调、异常回调、完成回调等功能。
DeferredResult
:使用方式与Callable
类似,但在返回结果上不一样,它返回的时候实际结果可能没有生成,实际的结果可能会在另外的线程里面设置到DeferredResult
中去。
WebFlux框架
- 背景:虽然Servlet对请求和响应都支持非阻塞处理,但SpringMVC异步请求过程中,需要先得到完整的HTTP请求数据,解析请求中的相关参数(同步获取请求),然后才能执行后续操作(异步发送响应)。因此需要WebFlux框架实现对请求和响应都是异步处理。
- 响应式编程:与面向对象中把数据相关的操作封装为对象不同,响应式编程把数据抽象为数据流及对数据流的操作与变化的响应。WebFlux的响应式模型由Spring自己的Reactor库来实现,Netty作为默认的WebFlux框架服务端。
- WebFlux框架下的HTTP请求处理过程:
- Netty接收HTTP连接,准备开始接收请求数据。
- Netty把请求封装为数据源,之后获取所有对该数据源的操作,但所有操作此时都不会被执行(因为数据可能尚未准备好,且数据源没有订阅者)。
- WebFlux框架封装包括处理器查找、处理器执行、返回值处理的逻辑,将这三种操作作为数据转换操作链接在数据源上,这些操作页还未执行。
- 所有的处理器逻辑的返回值都建议使用Flux(包含0n个元素)、Mono(包含01个元素)或其他异步结果进行,可做到对响应的数据写入也变为异步。
- 待所有操作链接完成后,在对请求进行响应时,会对数据源进行订阅以执行所有的转换逻辑,订阅后,只有当数据源准备好时,才会使用线程执行所有的转换操作,在数据转换逻辑中包含把数据写入响应的逻辑。
- 使用:
- 若想使用Reactive特性,需要把客户端换为支持Reactive特性的客户端。
- 由于其不是基于Servlet的,所以Servlet原生的一些类型都不能被直接使用。
- WebFlux把请求和响应封装在了一起,作为
ServerWebExchange
类型使用,可以通过其访问请求ServeWebExchange
、响应ServerHttpResponse
、SessionWebSession
。
- WebFlux建议处理器方法返回的结果为异步类型。
配置
- 在Spring Boot中提供了各种属性,通过修改其中的一些组件属性,可以实现对Spring MVC的定制,同时原始的Spring MVC支持使用配置器
WebMvcConfigurer
方式进行配置。
- 对Spring MVC进行定制,本质上就是通过提供工厂创建Bean需要使用的属性来定制产生的Spring MVC组件。
- Spring容器提供了通用的环境
Environment
用于包含当前运行应用的一些初始化属性,属性Key
的值是Value
。在工厂模式下,Bean的产生依赖的外部属性都存放在Environment
中。Environment
中包含多个属性源PropertySource
,而在Spring Boot中又对这些属性源进行了增强,支持以更多的方式为Environment
添加不同的属性源。
属性配置
- 系统属性(VM options):系统属性指当前应用所依赖的执行系统中配置的属性,Java中通过
System.getProperties()
方法来获取全部系统属性,Spring Boot把该方法获取的结果作为属性源来使用。系统内属性包括JVM相关属性,命令行参数指定属性等,在运行时可以通过System.setProperty(key, value)
动态添加系统属性。
- 环境变量(Environment variables):环境变量指当前应用的宿主系统的环境变量(Windows/Linux等),Java中通过
System.getenv()
方法来获取全部环境变量。
- 启动参数(Program arguments):启动参数是指min方法接收的String数组类型的参数。Spring Boot支持通过启动参数添加属性源,在启动参数中,以
--key:value
的格式表示要添加到属性源中的属性。
- 构建参数:使用
SpringApplicationBuilder
类来构建Spring应用,在构建时可以传入更多的自定义参数。
配置文件
- 配置文件位置:通过属性配置中的四种方法来指定配置文件的位置。(
spring.config.location
属性的值)
resources
目录:编译后其中文件会被放在与最上级包同级的文件夹,即编译后文件的根目录(classpath:/
)
resources/config
目录:classpath:/config/
- 当前应用启动目录:
file:./
- 当前应用启动目录下的config目录:
file:./config/
- 配置文件格式:
- properties:是Java标准定义的配置文件格式,提供
key=value
的方式配置属性,每个属性占一行。绑定功能包括通过.
分割多级属性,[int]
表示数组属性,[String]
表示字典属性等。
- yml:是使用YAML语言的配置文件格式,它对简单属性、多层属性、数组、字典等提供了原生的支持,还提供了对高级功能的支持。
- xml:通过标签表示属性名,冗余度很高,不建议使用。
- 配置文件命名:通过属性配置中的四种方法来指定配置文件名。(
spring.conofig.name
属性的值)。默认情况下,Spring Boot会使用application
作为配置文件名进行查找。
- 多环境配置:Spring通过Profile功能为不同的环境提供不同的Bean,Spring Boot为不同的profile提供了加载不同配置文件的功能。
- 可以为配置文件的文件名与文件后缀之间添加
-环境配置名
来为不同的环境应用不同的配置。对于不包含环境配置名的文件来说,则作为所有环境都需要加载的配置使用。可以通过spring.profiles.active
来指定当前活动的环境配置名,该属性可以通过属性配置中的四种方法来指定,也可以通过全局配置文件来指定。
- 在Spring中可以通过注解
@PropertySource
把执行的配置文件加载到当前环境中作为属性源,不支持yml格式文件。
WebMvcConfigurer
配置器:在SpringMVC框架启动时,会获取当前Spring容器中所有的WebMvcConfigurer
实现类Bean,把获取到的全部Bean作为配置器,并执行其中的的方法以对当前Web框架进行配置,该接口提供了多个方法,实现类通过实现接口中的方法(配置、扩展、覆盖)来达到配置的目的。
- 配置类方法:修改当前组件的特性。
- 扩展类方法:添加组件。
- 覆盖类方法:替换内置的默认组件实现。
DelegatingWebMvcConfiguration
配置器:该类中引用了当前Spring中所有WebMvcConfigurer
的实现类Bean,并通过调用它们的方法对MVC框架进行配置。继承此类可以更加底层的组件进行配置。
- 常用配置类方法:
- 异步请求:
configureAsyncSupport(AsyncSupportConfigurer)
- 路径匹配:
configurePathMatch(PathMatchConofigurer)
- 内容协商管理:
configureContentNegotiation(ContentNegotiationConfigurer)
- 消息转换器:
configureMessageConverters(List<HttpMessageConverter<?>>)
- 默认请求处理器:
configureDefaultServletHandling(DefaultServletHandlerConfigurer)
- 处理器异常解析器:
configureHandlerExceptionResolvers(List<HandlerExceptionResolver>)
- 配置与注册视图解析器:
configureViewResolvers(ViewResolverRegistry)
- 常用扩展类方法:
- 拦截器注册器:
addInterceptors(InterceptorRegistry)
- 函数解析器:
addArgumentResolvers(List<HandlerMethodArgumentResolver>)
- 返回值数据处理器:
addReturnValueHandlers(List<HandlerMethodReturnValueHandler>)
- 跨域请求全局配置注册器:
addCorsMappings(CorsRegistry)
- 格式与类型转换器:
addFomatters(FormatterRegistry)
- 处理器异常解析器:
extendHandlerExceptionResolvers(List<HandlerExceptionResolver>)
- 消息转换器:
extendMessageConverters(List<HttpMessageConverter<?>>)
- 注册视图控制器:
addViewControllers(ViewControllerRegistry)
- 注册资源处理器:
adRsourceHandlers(ResourceHandlerRegistry)
框架源码
- 入口:对于Spring Web应用,有两个最主要的入口,即应用启动入口和请求处理入口。
- 应用启动入口:
SpringApplication.run()
,该方法包括初始化全部Spring MVC相关的组件、把Spring MVC组件、Spring上下文与Web容器连接的过程,执行完成后Web应用进入就绪状态,等待接收HTTP请求。
- 请求处理入口:原始的请求处理入口是Web容器,负责接收HTTP请求,并把该请求分配给Spring MVC组件进行处理。Spring MVC的组件又通过一定的策略找到开发者编写的处理器方法,经过一定的与处理后执行处理器方法。(处理器方法不是真正的请求处理入口,但却是Spring框架层之外的可以追踪的请求处理过程的入口)
启动阶段
- 问题:
- Spring Boot如何启动Web容器?
- Spring Boot如何初始化MVC相关组件?
- Spring Boot如何把MVC组件与Web容器整合?
SpringApplication的创建
请求处理阶段
- 问题:
- Web容器接收请求后,Spring MVC如何把它交给处理器方法执行?
- 处理器方法的参数是如何从请求参数转换而来的?
- 处理器方法的返回值如何转换为响应内容返回?