Spring MVC 
浅谈 MVC
 
Model ,模型层,指工程中的 JavaBean,作用是处理数据 
 JavaBean 分为两类: 
一类称为实体类 Bean:专门存储业务数据 
一类称为业务处理 Bean:指 Service 或 Dao 对象,专门用于处理业务逻辑和数据访问  
 
View ,视图层,指工程中的html页面,作用是与用户进行交互,展示数据 
Controller ,控制层,指工程中的 servlet,作用是接收请求和响应浏览器 
MVC 工作流程
 
用户通过视图层发送请求到服务器,在服务器中请求被 Controller 接收 
Controller 调用相应的 Model 层处理请求,处理完毕将结果返回到 Controller 
Controller 再根据请求处理的结果找到相应的 View 视图,渲染数据后最终响应给浏览器  
 
Spring MVC
 
Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块 
Spring 家族原生产品 ,与 IOC 容器等基础设施无缝对接 
基于原生的 Servlet ,通过功能强大的 前端控制器 DispatcherServlet ,对请求和响应进行统一处理 
表述层各细分领域需要解决的问题 全方位覆盖 ,提供 全面解决方案  
代码清新简洁 ,大幅度提升开发效率 
内部组件化程度高,可插拔式组件 即插即用 ,想要什么功能配置相应组件即可 
性能卓著 ,尤其适合现代大型、超大型互联网项目要求 
 
QuickStart 导入依赖 
SpringMVC
 
1 2 3 4 5 <dependency >     <groupId > org.springframework</groupId >      <artifactId > spring-webmvc</artifactId >      <version > 5.3.1</version >  </dependency > 
 
logback
 
1 2 3 4 5 <dependency >     <groupId > ch.qos.logback</groupId >      <artifactId > logback-classic</artifactId >      <version > 1.2.3</version >  </dependency > 
 
ServletAPI
 
1 2 3 4 5 6 <dependency >     <groupId > javax.servlet</groupId >      <artifactId > javax.servlet-api</artifactId >      <version > 3.1.0</version >      <scope > provided</scope >  </dependency > 
 
Spring5和Thymeleaf
 
1 2 3 4 5 <dependency >     <groupId > org.thymeleaf</groupId >      <artifactId > thymeleaf-spring5</artifactId >      <version > 3.0.12.RELEASE</version >  </dependency > 
 
Web.xml 配置文件 
注册 SpringMVC 的前端控制器 DispatcherServlet 
 
默认配置方式 
此配置作用下,SpringMVC 的配置文件默认位于 WEB-INF  下,默认名称为-servlet.xml,例如,以下配置所对应   SpringMVC 的配置文件位于 WEB-INF 下,文件名为 springMVC-servlet.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <servlet >     <servlet-name > springMVC</servlet-name >      <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class >  </servlet > <servlet-mapping >     <servlet-name > springMVC</servlet-name >           <url-pattern > /</url-pattern >  </servlet-mapping > 
 
扩展配置方式 
标签 
说明 
 
 
init-param 
设置SpringMVC配置文件的位置和名称 
 
load-on-startup 
设置SpringMVC前端控制器DispatcherServlet的初始化时间 
 
 
 
servlet
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <servlet >     <servlet-name > springMVC</servlet-name >      <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class >           <init-param >                   <param-name > contextConfigLocation</param-name >                   <param-value > classpath:springMVC.xml</param-value >      </init-param >           <load-on-startup > 1</load-on-startup >  </servlet > 
 
servlet-mapping
 
1 2 3 4 5 6 7 8 9 <servlet-mapping >     <servlet-name > springMVC</servlet-name >           <url-pattern > /</url-pattern >  </servlet-mapping > 
 
 标签中使用/和/*的区别  
 
/所匹配的请求可以是/login或.html或.js或.css方式的请求路径,但是/不能匹配 .jsp请求路径的请求
因此就可以避免在访问jsp页面时,该请求被DispatcherServlet处理,从而找不到相应的页面 
 
/*则能够匹配所有请求 ,例如在使用过滤器时,若需要对所有请求进行过滤,就需要使用这种写法
 
 
请求控制器 由于前端控制器对浏览器发送的请求进行了统一的处理,但是具体的请求有不同的处理过程,因此需要创建处理具体请求的类,即请求控制器。
因为SpringMVC的控制器由一个 POJO(普通的Java类)担任,因此需要 通过 @Controller 注解将其标识为一个控制层组件,交给 Spring 的 IoC 容器管理 ,此时SpringMVC才能够识别控制器的存在 
1 2 3 @Controller public  class  HelloController  { } 
 
SpringMVC 配置文件 
包结构自动扫描
 
1 2 <context:component-scan  base-package ="com.cyan.mvc.controller" /> 
 
Thymeleaf 视图解析器
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <bean  id ="viewResolver"  class ="org.thymeleaf.spring5.view.ThymeleafViewResolver" >     <property  name ="order"  value ="1" />      <property  name ="characterEncoding"  value ="UTF-8" />      <property  name ="templateEngine" >          <bean  class ="org.thymeleaf.spring5.SpringTemplateEngine" >              <property  name ="templateResolver" >                  <bean  class ="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver" >                                                <property  name ="prefix"  value ="/WEB-INF/templates/" />                                                <property  name ="suffix"  value =".html" />                      <property  name ="templateMode"  value ="HTML5" />                      <property  name ="characterEncoding"  value ="UTF-8"  />                  </bean >              </property >          </bean >      </property >  </bean > 
 
处理静态资源
 
1 2 3 4 5 6 <mvc:default-servlet-handler /> 
 
开启 mvc 注解驱动
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <mvc:annotation-driven >     <mvc:message-converters >                   <bean  class ="org.springframework.http.converter.StringHttpMessageConverter" >              <property  name ="defaultCharset"  value ="UTF-8"  />              <property  name ="supportedMediaTypes" >                  <list >                      <value > text/html</value >                      <value > application/json</value >                  </list >              </property >          </bean >      </mvc:message-converters >  </mvc:annotation-driven > 
 
访问测试 
访问首页
 
在请求控制器中创建处理请求的方法 
注解 
说明 
 
 
@RequestMapping 
处理请求和控制器方法之间的映射关系,value属性可以通过请求地址匹配请求,/表示的当前工程的上下文路径 
 
 
 
1 2 3 4 5 @RequestMapping("/") public  String index ()  {         return  "index" ; } 
 
跳转页面
 
在主页index.html中设置超链接
1 2 3 4 5 6 7 8 9 10 11 <!DOCTYPE html > <html  lang ="en"  xmlns:th ="http://www.thymeleaf.org" > <head >     <meta  charset ="UTF-8" >      <title > 首页</title >  </head > <body >     <h1 > 首页</h1 >      <a  th:href ="@{/hello}" > HelloWorld</a > <br />  </body > </html > 
 
 在请求控制器中创建处理请求的方法 
1 2 3 4 @RequestMapping("/hello") public  String HelloWorld ()  {    return  "target" ; } 
 
小总结 
浏览器发送请求,若请求地址符合前端控制器的 url-pattern,该请求就会被 前端控制器  DispatcherServlet 处理。 
前端控制器会读取 SpringMVC 的核心配置文件,通过扫描组件 找到控制器 ,将请求地址和控制器中 @RequestMapping 注解的 value 属性值进行匹配,若匹配成功,该注解所标识的控制器方法就是处理请求的方法。 
处理请求的方法需要返回一个字符串类型的视图名称,该视图名称会 被视图解析器解析 ,加上前缀和后缀组成视图的路径,通过 Thymeleaf 对视图进行渲染,最终转发到视图所对应页面  
 
@RequestMapping 基本介绍 
注解说明
 
将 请求和处理请求  的控制器方法关联起来,建立 映射关系 。SpringMVC 接收到指定的请求,就会来找到在映射关系中对应的控制器方法来处理这个请求。
注解位置
 
位置 
说明 
 
 
标识一个类 
设置映射请求的请求路径的初始信息 
 
标识一个方法 
设置映射请求请求路径的具体信息 
 
 
 
1 2 3 4 5 6 7 8 9 10 @Controller @RequestMapping("/test") public  class  RequestMappingController  {         @RequestMapping("/testRequestMapping")      public  String testRequestMapping () {         return  "success" ;     } } 
 
参数说明 
value
 
value 属性通过请求的 请求地址  匹配请求映射
 
value 属性是一个 字符串类型的数组 ,表示该请求映射能够匹配多个请求地址所对应的请求
 
value 属性必须设置,至少通过请求地址匹配请求映射
 
 
method
 
method 属性通过请求的 请求方式 (get 或 post)匹配请求映射 
method 属性是一个 RequestMethod 类型的数组 ,表示该请求映射能够匹配多种请求方式的请求 
若当前请求的请求地址满足请求映射的 value 属性,但是请求方式不满足 method 属性,则浏览器报错 405:Request method ‘POST’ not supported 
 
params
 
params 属性通过请求的 请求参数  匹配请求映射 
params 属性是一个 字符串类型的数组 ,可以通过四种表达式设置请求参数和请求映射的匹配关系 
 
表达式 
说明 
 
 
param 
要求请求映射所匹配的请求必须携带param请求参数 
 
!param 
要求请求映射所匹配的请求必须不能携带param请求参数 
 
param=value 
要求请求映射所匹配的请求必须携带param请求参数且param=value 
 
param!=value 
要求请求映射所匹配的请求必须携带param请求参数但是param!=value 
 
 
 
headers
 
headers 属性通过请求的请求头信息匹配请求映射 
headers 属性是一个字符串类型的数组,可以通过四种表达式设置请求头信息和请求映射的匹配关系 
若当前请求满足 @RequestMapping 注解的 value 和 method 属性,但是不满足 headers 属性,此时页面显示 404 错误,即资源未找到 
 
表达式 
说明 
 
 
header 
要求请求映射所匹配的请求必须携带header请求头信息 
 
!header 
要求请求映射所匹配的请求必须不能携带header请求头信息 
 
header=value 
要求请求映射所匹配的请求必须携带header请求头信息且header=value 
 
header!=value 
要求请求映射所匹配的请求必须携带header请求头信息且header!=value 
 
 
 
ant 风格路径 
SpringMVC 支持 ant 风格的路径
 
表达式 
说明 
 
 
? 
表示任意的单个字符 
 
* 
表示任意的0个或多个字符 
 
** 
表示任意的一层或多层目录 
 
 
 
注意 :在使用 ** 时,只能使用 /**/xxx 的方式
路径方式 
SpringMVC 支持路径中的占位符
 
路径方式 
比较 
 
 
原始方式 
/deleteUser?id=1 
 
rest方式 
/deleteUser/1 
 
 
 
SpringMVC 路径中的占位符常用于 RESTful 风格中,当请求路径中将某些数据通过路径的方式传输到服务器中,就可以在相应的 @RequestMapping 注解的 value  属性中通过 占位符{xxx}  表示传输的数据,在 通过 @PathVariable 注解,将占位符所表示的数据赋值给控制器方法的形参  
注解 
说明 
 
 
@PathVariable 
将占位符所表示的数据赋值给控制器方法的形参 
 
 
 
1 <a  th:href ="@{/testRest/1/admin}" > 测试路径中的占位符-->/testRest</a > <br > 
 
1 2 3 4 5 6 @RequestMapping("/testRest/{id}/{username}") public  String testRest (@PathVariable("id")  String id, @PathVariable("username")  String username) {    System.out.println("id:" +id+",username:" +username);     return  "success" ; } 
 
注意事项 
@RequestMapping 的派生注解
 
派生注解 
说明 
 
 
@GetMapping 
处理get请求的映射 
 
@PostMapping 
处理post请求的映射 
 
@PutMapping 
处理put请求的映射 
 
@DeleteMapping 
处理delete请求的映射 
 
 
 
常用的请求方式有 get,post,put,delete
 
目前浏览器只支持 get 和 post,若在 form 表单提交时,为 method 设置了其他请求方式的字符串(put或delete),则按照默认的请求方式 get 处理  
若要发送 put 和 delete 请求,则需要通过 Spring 提供的过滤器 HiddenHttpMethodFilter   
 
请求参数 ServletAPI 获取 
API 
说明 
 
 
HttpServletRequest request 
将HttpServletRequest作为控制器方法的形参,此时HttpServletRequest类型的参数表示封装了当前请求的请求报文的对象  
 
 
 
1 2 3 4 5 6 7 @RequestMapping("/testParam") public  String testParam (HttpServletRequest request) {    String  username  =  request.getParameter("username" );     String  password  =  request.getParameter("password" );     System.out.println("username:" +username+",password:" +password);     return  "success" ; } 
 
控制器方法的形参获取 在控制器方法的形参位置,设置和请求参数同名的形参 ,当浏览器发送请求,匹配到请求映射时,在 DispatcherServlet 中就会将请求参数赋值给相应的形参 
1 <a  th:href ="@{/testParam(username='admin',password=123456)}" > 测试获取请求参数-->/testParam</a > <br > 
 
1 2 3 4 5 @RequestMapping("/testParam") public  String testParam (String username, String password) {    System.out.println("username:" +username+",password:" +password);     return  "success" ; } 
 
若请求所传输的请求参数中 有多个同名的请求参数 ,此时可以在控制器方法的 形参中设置字符串数组或者字符串类型的形参  接收此请求参数 
若使用 字符串数组类型  的形参,此参数的数组中包含了每一个数据 
若使用 字符串类型  的形参,此参数的值为每个数据中间使用逗号拼接的结果 
 
POJO 获取 可以在控制器方法的形参位置 设置一个实体类类型的形参 ,此时 若浏览器传输的请求参数的参数名和实体类中的属性名一致 ,那么请求参数就会为此属性赋值 
1 2 3 4 5 6 7 8 <form  th:action ="@{/testpojo}"  method ="post" >     用户名:<input  type ="text"  name ="username" > <br >      密码:<input  type ="password"  name ="password" > <br >      性别:<input  type ="radio"  name ="sex"  value ="男" > 男<input  type ="radio"  name ="sex"  value ="女" > 女<br >      年龄:<input  type ="text"  name ="age" > <br >      邮箱:<input  type ="text"  name ="email" > <br >      <input  type ="submit" >  </form > 
 
1 2 3 4 5 6 @RequestMapping("/testpojo") public  String testPOJO (User user) {    System.out.println(user);     return  "success" ; } 
 
@RequestParam @RequestParam 是将 请求参数  和控制器方法的 形参  创建映射关系
属性 
说明 
 
 
value 
指定为形参赋值的请求参数的参数名 
 
required 
设置是否必须传输 此请求参数,默认值为true 
 
defaultValue 
不管required属性值为true或false,当value所指定的请求参数没有传输或传输的值为””时,则使用默认值为形参赋值 
 
 
 
关于 required 
 
将 请求头信息  和控制器方法的 形参  创建映射关系
有三个属性:value、required、defaultValue,用法同@RequestParam
@CookieValue 将 cookie数据  和控制器方法的 形参  创建映射关系
有三个属性:value、required、defaultValue,用法同@RequestParam
乱码问题 解决获取请求参数的乱码问题,可以使用 SpringMVC 提供的编码过滤器 CharacterEncodingFilter ,但是必须在 web.xml  中进行注册 
SpringMVC 中处理编码的过滤器一定要配置到其他过滤器之前 ,否则无效
filter
 
1 2 3 4 5 6 7 8 9 10 11 12 13 <filter >     <filter-name > CharacterEncodingFilter</filter-name >      <filter-class > org.springframework.web.filter.CharacterEncodingFilter</filter-class >      <init-param >          <param-name > encoding</param-name >          <param-value > UTF-8</param-value >      </init-param >      <init-param >          <param-name > forceResponseEncoding</param-name >          <param-value > true</param-value >      </init-param >  </filter > 
 
filter-mapping
 
1 2 3 4 <filter-mapping >     <filter-name > CharacterEncodingFilter</filter-name >      <url-pattern > /*</url-pattern >  </filter-mapping > 
 
域对象共享数据 request域 ServletAPI 
使用 ServletAPI 向 request 域对象共享数据
 
1 2 3 4 5 @RequestMapping("/testServletAPI") public  String testServletAPI (HttpServletRequest request) {    request.setAttribute("testScope" , "hello,servletAPI" );     return  "success" ; } 
 
ModelAndView 
使用 ModelAndView 向 request 域对象共享数据
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RequestMapping("/testModelAndView") public  ModelAndView testModelAndView () {         ModelAndView  mav  =  new  ModelAndView ();          mav.addObject("testScope" , "hello,ModelAndView" );          mav.setViewName("success" );     return  mav; } 
 
map 
使用 map 向 request 域对象共享数据
 
1 2 3 4 5 @RequestMapping("/testMap") public  String testMap (Map<String, Object> map) {    map.put("testScope" , "hello,Map" );     return  "success" ; } 
 
ModelMap 
使用 ModelMap 向 request 域对象共享数据
 
1 2 3 4 5 @RequestMapping("/testModelMap") public  String testModelMap (ModelMap modelMap) {    modelMap.addAttribute("testScope" , "hello,ModelMap" );     return  "success" ; } 
 
Model、ModelMap、Map 的关系 Model、ModelMap、Map 类型的参数其实本质上都是 BindingAwareModelMap 类型的 
1 2 3 4 public  interface  Model {}public  class  ModelMap  extends  LinkedHashMap <String, Object> {}public  class  ExtendedModelMap  extends  ModelMap  implements  Model  {}public  class  BindingAwareModelMap  extends  ExtendedModelMap  {}
 
session 域 1 2 3 4 5 @RequestMapping("/testSession") public  String testSession (HttpSession session) {    session.setAttribute("testSessionScope" , "hello,session" );     return  "success" ; } 
 
application 域 1 2 3 4 5 6 @RequestMapping("/testApplication") public  String testApplication (HttpSession session) {    ServletContext  application  =  session.getServletContext();     application.setAttribute("testApplicationScope" , "hello,application" );     return  "success" ; } 
 
视图 
SpringMVC 中的视图是 View接口 ,视图的作用渲染数据,将模型 Model 中的数据展示给用户
 
SpringMVC 视图的种类很多,默认  有转发视图和重定向视图
 
当工程引入 jstl 的依赖,转发视图会自动转换为 JstlView
 
若使用的视图技术为 Thymeleaf,在 SpringMVC 的配置文件中配置了 Thymeleaf 的视图解析器,由此视图解析器解析之后所得到的是 ThymeleafView
 
 
ThymeleafView 当控制器方法中所设置的视图名称 没有任何前缀  时,此时的视图名称会被 SpringMVC 配置文件中所配置的视图解析器解析,视图名称拼接视图前缀和视图后缀所得到的最终路径,会通过 转发  的方式实现跳转 
1 2 3 4 @RequestMapping("/testHello") public  String testHello () {    return  "hello" ; } 
 
转发视图 SpringMVC 中 默认的转发视图  是 InternalResourceView 
SpringMVC 中创建转发视图的情况:
当控制器方法中所设置的视图名称以 forward: 为前缀时,创建 InternalResourceView  视图,此时的视图名称不会被 SpringMVC 配置文件中所配置的视图解析器解析,而是会将前缀 forward: 去掉,剩余部分作为最终路径通过 转发  的方式实现跳转
例如”forward:/“,“forward:/employee”
 
1 2 3 4 @RequestMapping("/testForward") public  String testForward () {    return  "forward:/testHello" ; } 
 
重定向视图 SpringMVC中 默认的重定向视图  是 RedirectView 
当控制器方法中所设置的视图名称以 redirect: 为前缀时,创建 RedirectView  视图,此时的视图名称不会被 SpringMVC 配置文件中所配置的视图解析器解析,而是会将前缀 redirect: 去掉,剩余部分作为最终路径通过 重定向  的方式实现跳转
例如”redirect:/“,“redirect:/employee”
 
1 2 3 4 @RequestMapping("/testRedirect") public  String testRedirect () {    return  "redirect:/testHello" ; } 
 
注:
 
重定向视图在解析时,会先将 redirect: 前缀去掉,然后会判断剩余部分是否以 / 开头,若是则会自动拼接上下文路径
面试:转发和重定向区别?
 
 转发(Forward): 
转发是服务器端的行为,当服务器接收到客户端的请求后,可以将请求转发给另一个资源(如另一个Servlet或JSP页面)进行处理 
在转发过程中,客户端浏览器并不知道服务器做了什么,它的地址栏中仍然显示原始请求的 URL 
转发是在服务器内部进行的,并且只需要一次客户端-服务器通信 
 
重定向(Redirect): 
重定向是通过 HTTP 状态码实现的一种客户端行为,服务器在接收到客户端的请求后,发送一个特定的 HTTP 响应码(如302 Found或301 Moved Permanently),将客户端重定向到另一个 URL 
在重定向中,客户端会收到一个新的 URL,并通过新的 URL 发送一个新的请求 
重定向会导致客户端发起两次请求-响应循环,因为它需要向服务器发送第二个请求以获取重定向位置的资源 
 
视图控制器 当控制器方法中,仅仅用来实现页面跳转 ,即只需要设置视图名称时,可以将处理器方法使用 view-controller  标签进行表示 
1 2 3 4 5 <mvc:view-controller  path ="/testView"  view-name ="success" > </mvc:view-controller > 
 
注 :
 
当 SpringMVC 中设置任何一个 view-controller  时,其他控制器中的请求映射将全部失效,此时需要在 SpringMVC 的核心配置文件中设置开启 mvc 注解驱动的标签:
1 <mvc:annotation-driven  /> 
 
RESTful 基本介绍 REST:Re presentational S tate T ransfer,表现层资源状态转移。
资源
 
将服务器看作是由很多离散的资源组成。一个资源可以由一个或多个 URI 来标识。URI 既是资源的名称,也是资源在 Web 上的地址。对某个资源感兴趣的客户端应用,可以通过资源的 URI 与其进行交互 
补充:  URI 代表统一资源标识符(Uniform Resource Identifier),是用于标识和定位抽象或物理资源的字符串序列。URI由一个紧密结构的字符序列组成,包括URL(统一资源定位符)和URN(统一资源名称)两种形式。
URL(统一资源定位符)是URI的一个常见形式,它提供了资源的位置和访问方法。它通常用于定位互联网上的资源,如网页、图片、视频等。例如,http://www.example.com/index.html  就是一个URL。
URN(统一资源名称)是另一种形式的URI,它通过名称唯一地标识资源而不考虑资源在何处以及如何访问。URN旨在通过命名空间和资源特定的标识符来命名资源。例如,urn:isbn:0451450523 是一个使用URN命名的书的标识符。
URI提供了一种统一的方式来标识和访问不同类型的资源,使得互联网上的资源可以被唯一地标识并定位。
资源的表述
 
资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交换)。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。 
状态转移
 
在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资源的表述,来间接实现操作资源的目的。 
RESTful的实现 
操作方式 
说明 
 
 
GET 
用来获取资源 
 
POST 
用来新建资源 
 
PUT 
用来更新资源 
 
DELETE 
用来删除资源 
 
 
 
REST风格提倡URL地址使用统一的风格设计,从前到后各个单词使用斜杠分开 ,不使用问号键值对方式携带请求参数 ,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。
操作 
传统方式 
REST风格 
 
 
查询操作 
getUserById?id=1 
user/1 –> get请求方式 
 
保存操作 
saveUser 
user –> post请求方式 
 
删除操作 
deleteUser?id=1 
user/1 –> delete请求方式 
 
更新操作 
updateUser 
user –> put请求方式 
 
 
 
HiddenHttpMethodFilter 
由于浏览器 只支持发送 get 和 post 方式的请求 , 该如何发送 put 和 delete 请求呢?
 
SpringMVC 提供了 HiddenHttpMethodFilter  帮助我们 将 POST 请求转换为 DELETE 或 PUT 请求 
HiddenHttpMethodFilter  处理 put 和 delete 请求的条件:
当前请求的请求方式必须为 post
 
当前请求必须传输请求参数 _method
 
 
满足以上条件,HiddenHttpMethodFilter  过滤器就会将当前请求的请求方式转换为 请求参数 _method 的值,因此 请求参数_method 的值才是最终的请求方式 
在web.xml中注册 HiddenHttpMethodFilter  
 
1 2 3 4 5 6 7 8 <filter >     <filter-name > HiddenHttpMethodFilter</filter-name >      <filter-class > org.springframework.web.filter.HiddenHttpMethodFilter</filter-class >  </filter > <filter-mapping >     <filter-name > HiddenHttpMethodFilter</filter-name >      <url-pattern > /*</url-pattern >  </filter-mapping > 
 
注 :
 
SpringMVC中提供了两个过滤器: CharacterEncodingFilter 和 HiddenHttpMethodFilter
在 web.xml 中注册时,必须先注册  CharacterEncodingFilter ,再注册  HiddenHttpMethodFilter
在 CharacterEncodingFilter 中通过 request.setCharacterEncoding(encoding) 方法设置字符集
 
request.setCharacterEncoding(encoding) 方法 要求前面不能有任何获取请求参数的操作 
 
而 HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作:
1 String  paramValue  =  request.getParameter(this .methodParam);
 
 
 
HttpMessageConverter 
@RequestBody 
方法 
说明 
 
 
@RequestBody 
可以获取请求体 ,需要在控制器方法设置一个形参,使用@RequestBody进行标识,当前请求的请求体就会为当前注解所标识的形参赋值  
 
 
 
1 2 3 4 5 <form  th:action ="@{/testRequestBody}"  method ="post" >     用户名:<input  type ="text"  name ="username" > <br >      密码:<input  type ="password"  name ="password" > <br >      <input  type ="submit" >  </form > 
 
1 2 3 4 5 6 @RequestMapping("/testRequestBody") public  String testRequestBody (@RequestBody  String requestBody) {    System.out.println("requestBody:" +requestBody);     return  "success" ; } 
 
@RequestEntity 
方法 
说明 
 
 
@RequestEntity 
封装请求报文 的一种类型,需要在控制器方法的形参中设置该类型的形参,当前请求的请求报文就会赋值给该形参 ,可以通过getHeaders()获取请求头信息,通过getBody()获取请求体信息 
 
 
 
1 2 3 4 5 6 @RequestMapping("/testRequestEntity") public  String testRequestEntity (RequestEntity<String> requestEntity) {    System.out.println("requestHeader:" +requestEntity.getHeaders());     System.out.println("requestBody:" +requestEntity.getBody());     return  "success" ; } 
 
@ResponseBody 
方法 
说明 
 
 
@ResponseBody 
用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器  
 
 
 
1 2 3 4 5 6 @RequestMapping("/testResponseBody") @ResponseBody public  String testResponseBody () {    return  "success" ; } 
 
@ResponseEntity 
方法 
说明 
 
 
@ResponseEntity 
用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文 
 
 
 
@RestController SpringMVC 提供的一个 复合注解 ,标识在控制器的类上,就 相当于为类添加了 @Controller 注解,并且为其中的每个方法添加了 @ResponseBody 注解 
作用
 
如果只是使用 @RestController 注解 Controller,则 Controller 中的方法 无法返回jsp页面 ,配置的视图解析器配置的视图解析器 InternalResourceViewResolver 不起作用,返回的内容就是Return 里的内容 。例如:本来应该到 success.jsp 页面的,则其显示 success;
如果需要 返回到指定页面 ,则需要用 @Controller 配合视图解析器 InternalResourceViewResolver 才行; 
如果需要 返回JSON、XML或自定义mediaType内容  到页面,则需要在对应的方法上加上 @ResponseBody 注解。 
 
仅使用 @Controller 注解
 
在对应的方法上,视图解析器可以解析 return 的 jsp,html 页面,并且跳转到相应页面; 
若返回 json 等内容到页面,则需要加 @ResponseBody 注解; 
 
使用 @RestController 注解
 
相当于 @Controller+@ResponseBody 两个注解的结合,返回 json 数据不需要在方法前面加 @ResponseBody 注解 了,但使用 @RestController 这个注解,就 不能返回jsp,html页面,视图解析器无法解析jsp,html页面。 
SpringMVC 处理 json @ResponseBody 处理 json 的步骤:
导入依赖
 
1 2 3 4 5 <dependency >     <groupId > com.fasterxml.jackson.core</groupId >      <artifactId > jackson-databind</artifactId >      <version > 2.12.1</version >  </dependency > 
 
开启注解驱动
 
在 SpringMVC 的核心配置文件中开启 mvc 的注解驱动,此时在 HandlerAdaptor 中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter ,可以将响应到浏览器的 Java 对象转换为 Json 格式的字符串 
1 <mvc:annotation-driven  /> 
 
在处理器方法上使用 @ResponseBody 注解进行标识。将 Java 对象直接作为控制器方法的返回值返回,就会自动转换为 Json 格式的字符串 
 
1 2 3 4 5 @RequestMapping("/testResponseUser") @ResponseBody public  User testResponseUser () {    return  new  User (1001 ,"admin" ,"123456" ,23 ,"男" ); } 
 
页面展示结果:
1 { “id”: 1001 , “username”: “admin”, “password”: “123456 ”, “age”: 23 , “sex”: “男”} 
 
SpringMVC 处理 ajax 
请求超链接
 
1 2 3 <div  id ="app" >     <a  th:href ="@{/testAjax}"  @click ="testAjax" > testAjax</a > <br >  </div > 
 
通过 vue 和 axios 处理点击事件
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <script type="text/javascript"  th :src="@{/static/js/vue.js}" ></script> <script  type ="text/javascript"  th:src ="@{/static/js/axios.min.js}" > </script > <script  type ="text/javascript" >     var  vue = new  Vue ({         el :"#app" ,         methods :{             testAjax :function  (event ) {                 axios ({                     method :"post" ,                     url :event.target .href ,                     params :{                         username :"admin" ,                         password :"123456"                      }                 }).then (function  (response ) {                     alert (response.data );                 });                 event.preventDefault ();             }         }     }); </script > 
 
控制器
 
1 2 3 4 5 6 @RequestMapping("/testAjax") @ResponseBody public  String testAjax (String username, String password) {    System.out.println("username:" +username+",password:" +password);     return  "hello,ajax" ; } 
 
文件上传和下载 文件下载 
文件下载实现思路
 
获取 ServletContext 对象 
通过该对象获取文件下载路径 
创建输入流 
将流读入字节数组 
设置响应头、状态码等信息放入 ResponseEntity 对象 
关闭输入流 
 
案例实现
 
使用 ResponseEntity 实现下载文件的功能,用于控制器方法的返回值类型,该控制器方法的返回值就是 响应到浏览器的响应报文 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @RequestMapping("/testDown") public  ResponseEntity<byte []> testResponseEntity(HttpSession session) throws  IOException {         ServletContext  servletContext  =  session.getServletContext();          String  realPath  =  servletContext.getRealPath("/static/img/1.jpg" );          InputStream  is  =  new  FileInputStream (realPath);          byte [] bytes = new  byte [is.available()];           is.read(bytes);          MultiValueMap<String, String> headers = new  HttpHeaders ();          MultiValueMap<String, String> headers = new  HttpHeaders ();          headers.add("Content-Disposition" , "attachment;filename=1.jpg" );          HttpStatus  statusCode  =  HttpStatus.OK;          ResponseEntity<byte []> responseEntity = new  ResponseEntity <>(bytes, headers, statusCode);          is.close();     return  responseEntity; } 
 
文件上传 
文件上传必要条件
 
条件 
说明 
 
 
form表单的请求方式 
必须为post  
 
添加属性 
enctype=“multipart/form-data 
 
 
 
SpringMVC 中将上传的文件封装到 MultipartFile 对象中,通过此对象可以获取文件相关信息
文件上传实现思路
 
通过 MultipartFile 对象获取文件名 
处理文件重名 
获取服务器中文件目录路径 
拼接最终上传路径 
通过 transferTo 实现上传 
 
引入依赖
 
1 2 3 4 5 <dependency >     <groupId > commons-fileupload</groupId >      <artifactId > commons-fileupload</artifactId >      <version > 1.3.1</version >  </dependency > 
 
添加配置
 
1 2 <bean  id ="multipartResolver"  class ="org.springframework.web.multipart.commons.CommonsMultipartResolver" > </bean > 
 
控制器
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @RequestMapping("/testUp") public  String testUp (MultipartFile photo, HttpSession session)  throws  IOException {         String  fileName  =  photo.getOriginalFilename();          String  hzName  =  fileName.substring(fileName.lastIndexOf("." ));     fileName = UUID.randomUUID().toString() + hzName;          ServletContext  servletContext  =  session.getServletContext();     String  photoPath  =  servletContext.getRealPath("photo" );     File  file  =  new  File (photoPath);     if (!file.exists()){         file.mkdir();     }     String  finalPath  =  photoPath + File.separator + fileName;          photo.transferTo(new  File (finalPath));     return  "success" ; } 
 
拦截器 拦截器和过滤器区别 过滤器 ,是在 java web 中将你传入的 request、response 提前过滤掉一些信息,或者提前设置一些参数。然后再传入 Servlet 的 action 进行业务逻辑处理 
拦截器 ,是面向切面编程的。就是在你的 Service 或者一个方法前调用一个方法,或者在方法后调用一个方法 
拦截器是基于 java 的反射机制的,而过滤器是基于函数的回调。  
拦截器不依赖于 servlet容器 ,而过滤器依赖于servlet容器。  
拦截器只对 action 请求起作用,而过滤器则可以对几乎所有的请求起作用  
拦截器可以访问 action 上下文、值、栈里面的对象,而过滤器不可以 
在 action 的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次  
拦截器可以获取 IOC 容器中的各个 bean,而过滤器不行 ,在拦截器里注入一个 service,可以调用业务逻辑  
拦截器与过滤器触发时机不一样, 过滤器是在请求进入容器后,但请求进入 servlet 之前进行预处理的。请求结束返回也是,拦截器是在 servlet 处理完后,返回给前端之前。  
 
拦截器的配置 SpringMVC 中的拦截器用于 拦截控制器方法的执行  ,SpringMVC 中的拦截器需要实现 HandlerInterceptor, SpringMVC 的拦截器必须在 SpringMVC 的配置文件中进行配置
1 2 3 4 5 6 7 8 9 10 11 <bean  class ="com.cyan.interceptor.FirstInterceptor" > </bean > <ref  bean ="firstInterceptor" > </ref > <mvc:interceptor >     <mvc:mapping  path ="/**" />      <mvc:exclude-mapping  path ="/testRequestEntity" />      <ref  bean ="firstInterceptor" > </ref >  </mvc:interceptor > 
 
拦截器的抽象方法 
方法 
说明 
 
 
preHandle 
控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行 ,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法  
 
postHandle 
控制器方法执行之后执行postHandle() 
 
afterComplation 
处理完视图和模型数据,渲染视图完毕之后执行afterComplation() 
 
 
 
多个拦截器执行顺序 
若每个拦截器的 preHandle() 都返回 true 
 
此时多个拦截器的执行顺序和拦截器 在 SpringMVC 的配置文件的配置顺序有关 : 
方法 
执行顺序 
 
 
preHandle() 
按照配置的顺序执行  
 
postHandle() 
按照配置的反序执行  
 
afterComplation() 
按照配置的反序执行  
 
 
 
若某个拦截器的 preHandle() 返回了 false 
 
方法 
执行顺序 
 
 
preHandle() 
preHandle()返回false和它之前的拦截器的preHandle()都会执行  
 
postHandle() 
preHandle()返回false和它之前的拦截器的postHandle()都不执行  
 
afterComplation() 
返回false的拦截器之前的拦截器的afterComplation()会执行  
 
 
 
异常处理 基于配置 SpringMVC 提供了一个处理控制器方法执行过程中 所出现的异常  的接口:HandlerExceptionResolver
HandlerExceptionResolver 实现类
 
DefaultHandlerExceptionResolver 
SimpleMappingExceptionResolver 
 
SpringMVC 提供了自定义的异常处理器 SimpleMappingExceptionResolver,使用方式:
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <bean  class ="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" >     <property  name ="exceptionMappings" >          <props >                           <prop  key ="java.lang.ArithmeticException" > error</prop >          </props >      </property >           <property  name ="exceptionAttribute"  value ="ex" > </property >  </bean > 
 
基于注解 
注解 
说明 
 
 
@ControllerAdvice 
将当前类 标识为异常处理的组件 
 
@ExceptionHandler 
用于设置所标识方法 处理的异常 
 
 
 
1 2 3 4 5 6 7 8 9 10 11 @ControllerAdvice public  class  ExceptionController  {    @ExceptionHandler(ArithmeticException.class)           public  String handleArithmeticException (Exception ex, Model model) {         model.addAttribute("ex" , ex);         return  "error" ;     } } 
 
SpringMVC 注解开发 
需求:
 
使用配置类和注解代替 web.xml 和 Spring MVC 配置文件的功能 
创建初始化类,代替 web.xml
 
在 Servlet 3.0 环境中,容器会在类路径中查找实现 javax.servlet.ServletContainerInitializer 接口的类,如果找到的话就 用它来配置Servlet容器 
Spring提供了这个接口的实现,名为 SpringServletContainerInitializer,这个类反过来又会查找实现  WebApplicationInitializer 的类并将配置的任务交给它们来完成。 
Spring 3.2 引入了一个便利的 WebApplicationInitializer 基础实现,名为 AbstractAnnotationConfigDispatcherServletInitializer,当我们的类扩展了它并将其部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置ServletContext。 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public  class  WebInit  extends  AbstractAnnotationConfigDispatcherServletInitializer  {         @Override      protected  Class<?>[] getRootConfigClasses() {         return  new  Class []{SpringConfig.class};     }          @Override      protected  Class<?>[] getServletConfigClasses() {         return  new  Class []{WebConfig.class};     }               @Override      protected  String[] getServletMappings() {         return  new  String []{"/" };     }          @Override      protected  Filter[] getServletFilters() {         CharacterEncodingFilter  encodingFilter  =  new  CharacterEncodingFilter ();         encodingFilter.setEncoding("UTF-8" );         encodingFilter.setForceRequestEncoding(true );         HiddenHttpMethodFilter  hiddenHttpMethodFilter  =  new  HiddenHttpMethodFilter ();         return  new  Filter []{encodingFilter, hiddenHttpMethodFilter};     } } 
 
创建 SpringConfig 配置类,代替 Spring 的配置文件 
 
1 2 3 4 @Configuration public  class  SpringConfig  {     } 
 
创建 WebConfig 配置类,代替 SpringMVC 的配置文件
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 @Configuration @ComponentScan("com.cyan.mvc.controller") @EnableWebMvc public  class  WebConfig  implements  WebMvcConfigurer  {         @Override      public  void  configureDefaultServletHandling (DefaultServletHandlerConfigurer configurer)  {         configurer.enable();     }          @Bean      public  CommonsMultipartResolver multipartResolver () {         return  new  CommonsMultipartResolver ();     }          @Override      public  void  addInterceptors (InterceptorRegistry registry)  {         FirstInterceptor  firstInterceptor  =  new  FirstInterceptor ();         registry.addInterceptor(firstInterceptor).addPathPatterns("/**" );     }             	@Override      public  void  addViewControllers (ViewControllerRegistry registry)  {         registry.addViewController("/" ).setViewName("index" );     }                @Override      public  void  configureHandlerExceptionResolvers (List<HandlerExceptionResolver> resolvers)  {         SimpleMappingExceptionResolver  exceptionResolver  =  new  SimpleMappingExceptionResolver ();         Properties  prop  =  new  Properties ();         prop.setProperty("java.lang.ArithmeticException" , "error" );                  exceptionResolver.setExceptionMappings(prop);                  exceptionResolver.setExceptionAttribute("ex" );         resolvers.add(exceptionResolver);     }           @Bean      public  ITemplateResolver templateResolver ()  {         WebApplicationContext  webApplicationContext  =  ContextLoader.getCurrentWebApplicationContext();                  ServletContextTemplateResolver  templateResolver  =  new  ServletContextTemplateResolver (                 webApplicationContext.getServletContext());         templateResolver.setPrefix("/WEB-INF/templates/" );         templateResolver.setSuffix(".html" );         templateResolver.setCharacterEncoding("UTF-8" );         templateResolver.setTemplateMode(TemplateMode.HTML);         return  templateResolver;     }          @Bean      public  SpringTemplateEngine templateEngine (ITemplateResolver templateResolver)  {         SpringTemplateEngine  templateEngine  =  new  SpringTemplateEngine ();         templateEngine.setTemplateResolver(templateResolver);         return  templateEngine;     }          @Bean      public  ViewResolver viewResolver (SpringTemplateEngine templateEngine)  {         ThymeleafViewResolver  viewResolver  =  new  ThymeleafViewResolver ();         viewResolver.setCharacterEncoding("UTF-8" );         viewResolver.setTemplateEngine(templateEngine);         return  viewResolver;     } } 
 
测试
 
1 2 3 4 @RequestMapping("/") public  String index () {    return  "index" ; } 
 
SpringMVC 执行流程 常用组件 
SpringMVC常用组件 
说明 
功能 
 
 
DispatcherServlet 
前端控制器 ,由框架提供 
统一处理请求和响应 ,整个流程控制的中心,由它调用其它组件处理用户的请求 
 
HandlerMapping 
处理器映射器 ,由框架提供 
根据请求的url、method等信息查找 Handler,即控制器方法  
 
Handler 
处理器 ,需要工程师开发 
在DispatcherServlet的控制下Handler对具体的用户请求进行处理 
 
HandlerAdapter 
处理器适配器 ,由框架提供 
通过HandlerAdapter对处理器(控制器方法)进行执行  
 
ViewResolver 
视图解析器 ,由框架提供 
行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView 
 
View 
视图  
将模型数据通过页面展示给用户 
 
 
 
DispatcherServlet 初始化过程 DispatcherServlet 本质上是一个 Servlet,所以天然的遵循 Servlet 的生命周期。所以宏观上是 Servlet 生命周期来进行调度。
 初始化 WebApplicationContext 
org.springframework.web.servlet.FrameworkServlet
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 protected  WebApplicationContext initWebApplicationContext ()  {    WebApplicationContext  rootContext  =          WebApplicationContextUtils.getWebApplicationContext(getServletContext());     WebApplicationContext  wac  =  null ;     if  (this .webApplicationContext != null ) {                  wac = this .webApplicationContext;         if  (wac instanceof  ConfigurableWebApplicationContext) {             ConfigurableWebApplicationContext  cwac  =  (ConfigurableWebApplicationContext) wac;             if  (!cwac.isActive()) {                                                   if  (cwac.getParent() == null ) {                                                               cwac.setParent(rootContext);                 }                 configureAndRefreshWebApplicationContext(cwac);             }         }     }     if  (wac == null ) {                                             wac = findWebApplicationContext();     }     if  (wac == null ) {                           wac = createWebApplicationContext(rootContext);     }     if  (!this .refreshEventReceived) {                                    synchronized  (this .onRefreshMonitor) {                          onRefresh(wac);         }     }     if  (this .publishContext) {                           String  attrName  =  getServletContextAttributeName();         getServletContext().setAttribute(attrName, wac);     }     return  wac; } 
 
创建 WebApplicationContext 
org.springframework.web.servlet.FrameworkServlet
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 protected  WebApplicationContext createWebApplicationContext (@Nullable  ApplicationContext parent)  {    Class<?> contextClass = getContextClass();     if  (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {         throw  new  ApplicationContextException (             "Fatal initialization error in servlet with name '"  + getServletName() +             "': custom WebApplicationContext class ["  + contextClass.getName() +             "] is not of type ConfigurableWebApplicationContext" );     }          ConfigurableWebApplicationContext  wac  =          (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);     wac.setEnvironment(getEnvironment());          wac.setParent(parent);     String  configLocation  =  getContextConfigLocation();     if  (configLocation != null ) {         wac.setConfigLocation(configLocation);     }     configureAndRefreshWebApplicationContext(wac);     return  wac; }  
 
DispatcherServlet 初始化策略 
org.springframework.web.servlet.DispatcherServlet
 
FrameworkServlet 创建 WebApplicationContext 后,刷新容器,调用 onRefresh(wac),此方法在DispatcherServlet 中进行了重写,调用了 initStrategies(context) 方法,初始化策略,即初始化 DispatcherServlet 的各个组件
1 2 3 4 5 6 7 8 9 10 11 protected  void  initStrategies (ApplicationContext context)  {   initMultipartResolver(context);    initLocaleResolver(context);    initThemeResolver(context);    initHandlerMappings(context);    initHandlerAdapters(context);    initHandlerExceptionResolvers(context);    initRequestToViewNameTranslator(context);    initViewResolvers(context);    initFlashMapManager(context); }  
 
调用组件处理请求 
processRequest() 
org.springframework.web.servlet.FrameworkServlet
 
FrameworkServlet 重写 HttpServlet中 的 service() 和 doXxx(),这些方法中调用了 processRequest(request, response)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 protected  final  void  processRequest (HttpServletRequest request, HttpServletResponse response)     throws  ServletException, IOException {     long  startTime  =  System.currentTimeMillis();     Throwable  failureCause  =  null ;     LocaleContext  previousLocaleContext  =  LocaleContextHolder.getLocaleContext();     LocaleContext  localeContext  =  buildLocaleContext(request);     RequestAttributes  previousAttributes  =  RequestContextHolder.getRequestAttributes();     ServletRequestAttributes  requestAttributes  =  buildRequestAttributes(request, response, previousAttributes);     WebAsyncManager  asyncManager  =  WebAsyncUtils.getAsyncManager(request);     asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new  RequestBindingInterceptor ());     initContextHolders(request, localeContext, requestAttributes);     try  {                  doService(request, response);     }     catch  (ServletException | IOException ex) {         failureCause = ex;         throw  ex;     }     catch  (Throwable ex) {         failureCause = ex;         throw  new  NestedServletException ("Request processing failed" , ex);     }     finally  {         resetContextHolders(request, previousLocaleContext, previousAttributes);         if  (requestAttributes != null ) {             requestAttributes.requestCompleted();         }         logResult(request, response, failureCause, asyncManager);         publishRequestHandledEvent(request, response, startTime, failureCause);     } }  
 
doService()
org.springframework.web.servlet.DispatcherServlet
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @Override protected  void  doService (HttpServletRequest request, HttpServletResponse response)  throws  Exception {    logRequest(request);               Map<String, Object> attributesSnapshot = null ;     if  (WebUtils.isIncludeRequest(request)) {         attributesSnapshot = new  HashMap <>();         Enumeration<?> attrNames = request.getAttributeNames();         while  (attrNames.hasMoreElements()) {             String  attrName  =  (String) attrNames.nextElement();             if  (this .cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {                 attributesSnapshot.put(attrName, request.getAttribute(attrName));             }         }     }          request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());     request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this .localeResolver);     request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this .themeResolver);     request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());     if  (this .flashMapManager != null ) {         FlashMap  inputFlashMap  =  this .flashMapManager.retrieveAndUpdate(request, response);         if  (inputFlashMap != null ) {             request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));         }         request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new  FlashMap ());         request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this .flashMapManager);     }     RequestPath  requestPath  =  null ;     if  (this .parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {         requestPath = ServletRequestPathUtils.parseAndCache(request);     }     try  {                  doDispatch(request, response);     }     finally  {         if  (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {                          if  (attributesSnapshot != null ) {                 restoreAttributesAfterInclude(request, attributesSnapshot);             }         }         if  (requestPath != null ) {             ServletRequestPathUtils.clearParsedRequestPath(request);         }     } } 
 
doDispatch() 
org.springframework.web.servlet.DispatcherServlet
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 protected  void  doDispatch (HttpServletRequest request, HttpServletResponse response)  throws  Exception {    HttpServletRequest  processedRequest  =  request;     HandlerExecutionChain  mappedHandler  =  null ;     boolean  multipartRequestParsed  =  false ;     WebAsyncManager  asyncManager  =  WebAsyncUtils.getAsyncManager(request);     try  {         ModelAndView  mv  =  null ;         Exception  dispatchException  =  null ;         try  {             processedRequest = checkMultipart(request);             multipartRequestParsed = (processedRequest != request);                                       mappedHandler = getHandler(processedRequest);             if  (mappedHandler == null ) {                 noHandlerFound(processedRequest, response);                 return ;             }                                          HandlerAdapter  ha  =  getHandlerAdapter(mappedHandler.getHandler());                          String  method  =  request.getMethod();             boolean  isGet  =  "GET" .equals(method);             if  (isGet || "HEAD" .equals(method)) {                 long  lastModified  =  ha.getLastModified(request, mappedHandler.getHandler());                 if  (new  ServletWebRequest (request, response).checkNotModified(lastModified) && isGet) {                     return ;                 }             }                                       if  (!mappedHandler.applyPreHandle(processedRequest, response)) {                 return ;             }                                       mv = ha.handle(processedRequest, response, mappedHandler.getHandler());             if  (asyncManager.isConcurrentHandlingStarted()) {                 return ;             }             applyDefaultViewName(processedRequest, mv);                          mappedHandler.applyPostHandle(processedRequest, response, mv);         }         catch  (Exception ex) {             dispatchException = ex;         }         catch  (Throwable err) {                                       dispatchException = new  NestedServletException ("Handler dispatch failed" , err);         }                  processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);     }     catch  (Exception ex) {         triggerAfterCompletion(processedRequest, response, mappedHandler, ex);     }     catch  (Throwable err) {         triggerAfterCompletion(processedRequest, response, mappedHandler,                                new  NestedServletException ("Handler processing failed" , err));     }     finally  {         if  (asyncManager.isConcurrentHandlingStarted()) {                          if  (mappedHandler != null ) {                 mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);             }         }         else  {                          if  (multipartRequestParsed) {                 cleanupMultipart(processedRequest);             }         }     } } 
 
processDispatchResult() 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 private  void  processDispatchResult (HttpServletRequest request, HttpServletResponse response,                                    @Nullable  HandlerExecutionChain mappedHandler, @Nullable  ModelAndView mv,                                    @Nullable  Exception exception)  throws  Exception {    boolean  errorView  =  false ;     if  (exception != null ) {         if  (exception instanceof  ModelAndViewDefiningException) {             logger.debug("ModelAndViewDefiningException encountered" , exception);             mv = ((ModelAndViewDefiningException) exception).getModelAndView();         }         else  {             Object  handler  =  (mappedHandler != null  ? mappedHandler.getHandler() : null );             mv = processHandlerException(request, response, handler, exception);             errorView = (mv != null );         }     }          if  (mv != null  && !mv.wasCleared()) {                  render(mv, request, response);         if  (errorView) {             WebUtils.clearErrorRequestAttributes(request);         }     }     else  {         if  (logger.isTraceEnabled()) {             logger.trace("No view rendering, null ModelAndView returned." );         }     }     if  (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {                  return ;     }     if  (mappedHandler != null ) {                           mappedHandler.triggerAfterCompletion(request, response, null );     } } 
 
SpringMVC 的执行流程 
用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器 ); 
由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器 ),并返回一个执行链(HandlerExecutionChain)。 
DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器 ) 
HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller ); 
Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息); 
HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ; 
DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器 )对视图进行解析; 
ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet; 
DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 
视图中的 request 域,生成最终的 View(视图 ); 
视图负责将结果显示到浏览器(客户端 )。