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(视图 );
视图负责将结果显示到浏览器(客户端 )。