Files
rikako-note/spring/Spring core/SpringMVC.md
wu xiangkai 56e693cfa0 日常提交
2022-11-18 17:30:04 +08:00

186 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SpringMVC
## SpringMVC简介
SpringMVC是一款基于Servlet API的web框架并基于前端控制器的模式被设计。前端控制器有一个中央的控制器DispatcherServlet通过一个共享的算法来集中分发请求请求实际是通过可配置的委托组件@Controller类)来处理的。
> Spring Boot遵循不同的初始化流程。相较于hooking到servlet容器的生命周期中Spring Boot使用Spring配置来启动其自身和内置servlet容器。filter和servlet声明在在spring配置中被找到并且被注入到容器中。
## DispatcherServlet
### Context层次结构
DispatcherServlet需要一个WebApplicationContextApplicationContext的拓展类Spirng容器来进行配置。WebApplicationContext拥有一个指向ServletContext和与ServletContext关联Servlet的链接。
**同样的也可以通过ServeltContext来获取关联的ApplicationContext可以通过RequestContextUtils中的静态方法来获取ServletContext关联的ApplicationContext。**
#### WebApplicationContext的继承关系
对大多数应用含有一个WebApplicationContext就足够了。也可以存在一个Root WebApplicationContext在多个DispatcherServlet实例之间共享同时DispatcherServlet实例也含有自己的WebApplicationContext。
通常被共享的root WebApplicationContext含有repository或service的bean对象这些bean对象可以被多个DispatcherServlet实例的子WebApplicationContext共享。同时子WebApplicationContext在继承root WebApplicationContext中bean对象的同时还能对root WebApplicationContext中的bean对象进行覆盖。
> #### WebApplicationContext继承机制
> 只有当Servlet私有的子WebApplicationContext中没有找到bean对象时才会从root WebApplicationContext中查找bean对象此行为即是对root WebApplicationContext的继承
### Spring MVC中特殊的bean类型
DispatcherServlet将处理请求和渲染返回的工作委托给特定的bean对象。Spring MVC中核心的bean类型如下。
#### HandlerMapping
将请求映射到handler和一系列用于pre/post处理的拦截器。
#### HandlerAdapter
HandlerAdapter主要是用于帮助DispatcherServlet调用request请求映射到的handler对象。
通常在调用含有注解的controller时需要对注解进行解析而HandlerAdapter可以向DispatcherServlet隐藏这些细节DispatcherServlet不必关心handler是如何被调用的。
#### HandlerExceptionResolver
解析异常的策略可能将异常映射到某个handler或是映射到html error页面。
#### ViewResolver
将handler返回的基于字符串的view名称映射到一个特定的view并通过view来渲染返回的响应。
### Web MVC Config
在应用中可以定义上小节中包含的特殊类型bean对象。DispatcherServlet会检查WebApplicationContext中存在的特殊类型bean对象如果某特殊类型的bean对象在容器中不存在那么会存在一个回滚机制使用DispatcherServlet.properties中默认的bean类型来创造bean对象例如DispatcherServlet.properties中指定的默认HandlerMapping类型是 org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping和org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping.)。
### Servlet Config
在Servlet环境中可以选择通过java代码的方式或者web.xml的方式来配置servlet容器。
配置Servlet的详细方式参照Spring MVC文档。
### 请求处理过程
DispatcherServlet按照如下方式来处理Http请求
1. 首先会找到WebApplicationContext并且将其绑定到请求中此后controller和其他元素在请求处理的过程中可以使用该WebApplicationContext。**默认情况下WebApplicationContext被绑定到DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE属性中**
2. locale resolver被绑定到请求中在请求处理过程中可以被其他元素使用
3. theme resolver被绑定到请求中在请求处理过程中可以被其他元素使用
4. 如果指定了multipart file resolver请求会被检查是否存在multipart。如果multipart被找到该请求会被包装在一个MultipartHttpServletRequest中并等待对其进行进一步的处理
5. 一个合适的handler将会被找到并且和该handler相关联的execution chainpre/post/Controller)会被执行返回一个model用于渲染。
6. 如果model被返回那么返回的model会被渲染。如果model没有返回那么没有view会被渲染。
在WebApplicationContext中声明的HandlerExceptionResolver会用于解析处理请求中抛出的异常。
### 路径匹配
Servlet API将完整的请求路径暴露为requestURI并且进一步划分requestURI为如下部分contextPathservletPathpathInfo。
> #### contextPath, servletPath, pathInfo区别
> - contextPathcontextPath为应用被访问的路径是整个应用的根路径。默认情况下SpringBoot的contextPath为"/"。可以通过server.servlet.context-path="/demo"来改变应用的根路径。
> - servletPathservletPath代表main DispatcherServlet的路径。默认情况下servletPath的值仍为"/"。可以通过spring.mvc.servlet.path来自定义该值。
### Interception
所有HandlerMapping的实现类都支持handler interceptor当想要为特定请求执行指定逻辑时会相当有用。拦截器必须要实现HandlerInterceptor该接口提供了三个方法
- preHandle在实际handler处理之前
- postHandle在 实际handler处理之后
- afterCompletion在请求完成之后
#### preHandle
preHandle方法会返回一个boolean值可以通过指定该方法的返回值来阻止执行链继续执行。当preHandle的返回值是true时后续执行链会继续执行当返回值是false时DispatcherServlet会假设该拦截器本身已经处理了请求并不会继续执行execution chain中的其他拦截器和实际handler。
#### postHandle
对于@ResponseBody或返回值为ResponseEntity的方法postHandle不起作用这些方法在HandlerAdapter中已经写入且提交了返回响应时间位于postHandle之前。到postHandle方法执行时已经无法再对响应做任何修改如添加header也不再被允许。对于这些场景可以**实现ResponseBodyAdvice或者声明一个ControllerAdvice**。
#### afterCompletion
调用时机位于DispaterServlet渲染view之后。
### Exceptions
如果异常在request mapping过程中被抛出或者从request handler中被抛出DispatcherServlet会将改异常委托给HandlerExceptionResolver chain来处理通常是返回一个异常响应。
如下是可选的HandlerExceptionResolver实现类
1. SimpleMappingExceptionResolver一个从exception class name到error view name的映射用于渲染错误页面
2. DefaultHandlerExceptionResolver解析Spring MVC抛出的异常并且将它们映射为Http状态码
3. ResponseStatusExceptionResolver通过@ResponseStatus注解来指定异常的状态码并且将异常映射为Http状态码状态码的值为@ResponseStatus注解指定的值
4. ExceptionHandlerExceptionResolver通过@Controller类内@ExceptionHandler方法或@ControllerAdvice类来解析异常
#### Resolver Chain
可以声明多个HandlerExceptionResolver bean对象来声明一个Exception Resolver链并可以通过指定order属性来指定resolver chain中的顺序order越大resolver在链中的顺序越靠后。
#### exception resolver的返回规范
HandlerExceptionResolver返回值可以按照如下规范返回
- 一个指向ModelAndView的error view
- 一个空的ModelAndView代表异常已经在该Resolver中被处理
- null代表该exception仍未被解析resolver chain中后续的resolver会继续尝试处理该exception如果exception在chain的末尾仍然存在该异常会被冒泡到servlet container中
#### container error page
如果exception直到resolver chain的最后都没有被解析或者response code被设置为错误的状态码如4xx,5xx)servlet container会渲染一个默认的error html page。
### 视图解析
#### 处理
类似于Exception Resolver也可以声明一个view resolver chain并且可以通过设置order属性来设置view resolver在resolver chain中的顺序。
view resolver返回null时代表该view无法被找到。
#### 重定向
以“redirect”前缀开头的view name允许执行重定向redirect之后指定的是重定向的url。
```shell
# 重定向实例如下
# 1. 基于servlet context重定向
redirect:/myapp/some/resource
# 2. 基于绝对路径进行重定向
redirect:https://myhost.com/some/arbitrary/path
```
> 如果controller的方法用@ResponseStatus注解标识该注解值的优先级**高于**RedirectView返回的response status。
#### 转发
以“forward”前缀开头的view name会被转发。
## Controller
### AOP代理
在某些时候可能需要AOP代理来装饰Controller对于Controller AOP推荐使用基于class的aop。
例如想要为@Controller注解的类添加@Transactional注解,此时需要手动指定@Transactional注解的proxyTargetClass=true来启用基于class的动态代理。
> 当为@Transactional注解指定了proxyTargetClass=true之后其不光会将@Transactional的代理变为基于cglib的还会将整个context中所有的autoproxy bean代理方式都变为基于cglib类代理的
### Request Mapping
可以通过@RequestMapping来指定请求对应的url、http请求种类、请求参数、header和media type。
还可以使用如下注解来映射特定的http method
- @GetMapping
- @PostMapping
- @DeleteMapping
- @PutMapping
- @PatchMapping
相对于上述的注解,@RequestMapping映射所有的http method。
#### URI Pattern
Spring MVC支持如下URI Pattern
- /resources/ima?e.png : ?匹配一个字符
- /resources/*.png : *匹配0个或多个字符但是位于同一个path segment内
- /resource/** : **可以匹配多个path segment但是\*\*只能用于末尾)
- /projects/{project}/versions : 匹配一个path segment并且将该path segment捕获到一个变量中变量可以通过@PathVariable访问
- /projects/{project:[a-z]+}/versions : 匹配一个path segment并且该path segment需要满足指定的regex
{varName:regex}可以将符合regex的path segment捕获到varName变量中例如
```java
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
// ...
}
```
#### Pattern Comparasion
如果多个pattern都匹配当前URI那么最佳匹配将会被采用。
多个pattern中更加具体的pattern会被采用。URI的计分方式如下
- 每个URI变量计1分
- *符号计1分
- **符号计两分
- 如果分数相同那么更长的pattern会被选择
- 如果分数和pattern length都相同那么拥有比wildchar更多的的URI变量的模式会被选择
分数越高那么pattern将会是更佳的匹配
#### 消费media-type
可以在@RequestMapping中指定请求中的Content-Type类型来缩小请求映射范围
```java
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
// ...
}
```
consumes属性还支持“非”的操作如下所示
```java
// 其映射除text/plain之外的content-type
@PostMapping(path = "/pets", consumes = "!text/plain")
```
#### 产生media-type
可以在@RequestMapping中指定produces属性来根据http请求header中的Accept属性来缩小映射范围
```java
// 该方法会映射Accept属性为application/json的http请求
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
```
#### Parameters & Headers
可以通过请求参数或header来缩小@RequestMapping的映射
支持过滤条件为某参数是否存在、某参数值是否为预期值header中某值是否存在、header中某值是否等于预期值。
```java
// 参数是否存在 myParam
// 参数是否不存在 !myParam
// 参数是否为预期值 myParam=myValue
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
// headers校验同样类似于params
@GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
```
### handler method
#### 类型转换
当handler方法的参数为非String类型时需要进行类型转换。在此种场景下类型转换是自动执行的。默认情况下简单类型int,long,Date,others)是被支持的。
可以通过WebDataBinder来进行自定义类型转换。
在执行类型转换时,空字符串""在转换为其他类型时可能被转化为null如转化为long,int,Date).如果允许null被注入给参数**需要将参数注解的required属性指定为false或者为参数指定@Nullable属性。**
#### Matrix Variable
在Matrix Variable中允许在uri中指定key-value对。path segment中允许包含key-value对其中变量之间通过分号分隔值如果存在多个多个值之间通过逗号分隔。
```shell
/cars;color=red,green;year=2012
```