文章目录
- 一、视图
- 1.1 视图对象View
- 1.2 ThymeleafView
- 1.3 转发视图
- 1.4 重定向视图
- 1.5 视图控制器
- 二、RESTFul
- 2.1 简介
- 2.2 PUT和DELETE请求的实现
- 2.2.1 HiddenHttpMethodFilter过滤器
- 2.2.2 实现PUT请求
- 2.2.3 实现DELETE请求
学习视频🎥:https://www.bilibili.com/video/BV1Ry4y1574R
一、视图
1.1 视图对象View
💬概述:Spring MVC中的视图对象为View
接口类型,跟Model
接口一样,View
接口也是Spring MVC的底层接口
🔑作用:封装视图数据,对页面进行渲染,将model的模型数据展现给用户
🔑视图类型:Spring MVC默认的转发视图InternalResourceView和重定向视图RedirectView,如果引入了相关依赖,还有JstlView、ThymeleafView等
🔑在源码中查看视图对象
-
在控制器方法中打上断点,debug模式启动,在方法栈中找到前端控制器
DispatcherServlet
的方法doDispatch()
-
进入
doDispatch()
方法中,可以看到前端控制器获取mv
对象之后,把mv
对象传给processDispatchResult()
方法,,该方法就是执行转发结果的方法,而mv
对象中封装了视图信息(或者说视图对象),所以需要进入processDispatchResult()
方法中查看视图对象 -
在
processDispatchResult()
方法中,mv
对象又被传给render()
方法,该方法实现了对视图进行渲染的功能 -
进入
render()
方法中,可以看到一个resovleViewName()
方法,该方法根据视图名对视图进行解析并返回一个解析之后的view
对象,该对象就是Spring MVC中的视图对象💡 不同视图名称写法决定了解析之后获取到的视图对象的具体类型,即每一种视图对象对应的视图名称写法不同,下面详细介绍
1.2 ThymeleafView
💬概述:被thymeleaf视图解析器解析之后的视图就是ThymeleafView视图,即ThymeleafView
类型的视图
💡 查看源码可知,
ThymeleafView
类间接实现了View
接口
🔑对应的视图名称设置:没有任何前缀和后缀,只有一个视图名,如"index"
🔑视图创建过程:执行控制器方法时,Spring MVC检测到返回值(视图名称)没有任何前缀和后缀,就会直接将视图名称交给在Spring MVC配置文件(springmvc.xml)中配置的thymeleaf视图解析器解析,然后thymeleaf视图解析器对该格式的视图名进行解析,此时得到的视图对象就为ThymeleafView
类型
🔑测试
① 创建测试控制器方法,方法返回值,即设置的视图名称不添加任何前缀和后缀(与之前的写法一样)
@RequestMapping("testView01")
public String showTestView01() {
return "success";
}
② debug模式启动,进入前端控制器DispatcherServlet
的render()
方法中,查看view
对象的具体类型信息
1.3 转发视图
💬概述:Spring MVC默认的转发视图类型为InternalResourceView
,如果在工程中引入jstl相关依赖,则转发视图会自动转换成JstlView
类型
🔑对应的视图名称设置:前缀——forward:
再加上要转发的路径,如"forward:/testView01"
❓ 关于要转发的路径
- 要转发的路径可以为
/
,整个视图名称为forward:/
,则表示请求转发到首页- 要转发的路径可以写相对路径,如
forward:testView01
,但不推荐写相对路径(相对路径与绝对路径区别)
🔑视图的创建过程
💡 创建转发视图过程中,会生成多个不同类型的视图对象,因为转发过程会出现不同的视图名称,而且配置不同视图解析器也会有影响
① 执行控制器方法时,Spring MVC检测到返回值(视图名称)带了forward:
的前缀,就不会将视图名称交给视图解析器解析,而是先将前缀forward:
去掉,剩下部分作为请求转发的跳转路径(转发的URL),此时得到的视图对象为InternalResourceView
类型(Spring MVC中的默认转发视图类型),然后Spring MVC会根据转发的路径找到与之匹配的控制器方法并执行
② 一般最后执行的控制器方法中,返回值都是没有前缀和后缀的视图名(目标页面),所以Spring MVC还会最后的视图名交给thymeleaf视图解析器解析,此时又会生成一个视图对象,为ThymeleafView
类型,thymeleaf解析之后就直接跳转到目标页面
🔑测试
① 创建测试控制器方法,视图名称设置为forward:/testView01
,表示请求转发到请求路径为/testView01
的控制器方法showTestView01()
@RequestMapping("/testView02")
public String showTestView02() {
return "forward:/testView01";
}
② 执行控制器方法showTestView02()
时的view
视图对象
③ 执行控制器方法showTestView01()
时的视图对象
1.4 重定向视图
💬概述:Spring MVC中默认的重定向视图类型为RedirectView
🔑对应的视图名称设置:前缀——redirect:
加上重定向跳转的路径,如"redirect:/testView02"
❓ 关于重定向的路径
重定向的路径也可以为
/
,即整个视图名称设置为redirect:/
,表示重定向到首页,一般在作用于增删改操作之后重定向视图在解析时,Spring MVC会检测重定向的路径(视图名称中去掉前缀的剩余部分),如果路径中有
/
,即路径为绝对路径时,Spring MVC会自动拼接上下文路径(工程路径),所以在写视图名称时只需写最终的请求路径或资源路径,不需要手动加上上下文路径
🔑视图的创建过程(与转发视图类似)
① 执行控制器方法时,Spring MVC检测到返回值(视图名称)带有redirect:
的前缀,就不会把视图名称交给视图解析器解析,而是先将前缀redirect:
去掉,剩余部分作为最终重定向跳转的路径(跳转的URL),此时得到的视图对象为RedirectView
类型(Spring MVC中默认的重定向视图类型),然后Spring MVC再根据重定向的路径找到对应的控制器方法并执行(获取到重定向路径时已经是浏览器发起的第二次请求)
② 一般最后执行的控制器方法中,返回值都是没有前缀和后缀的视图名(目标页面),所以Spring MVC还会最后的视图名交给thymeleaf视图解析器解析,此时又会生成一个视图对象,为ThymeleafView
类型,thymeleaf解析之后就直接跳转到目标页面
🔑测试
① 创建测试控制器方法,视图名称设置为redirect:/testView01
,表示重定向到请求路径为/testView01
的控制器方法showTestView01()
@RequestMapping("/testView03")
public String showTestView03() {
return "redirect:/testView01";
}
② 执行控制器方法showTestView03()
时的视图对象view
③ 执行控制器方法showTestView01()
时的视图对象
1.5 视图控制器
💬概述:如果某个控制器只是用来实现页面的跳转,没有进行其他处理,即仅仅设置了一个视图名并返回,则可以在Spring MVC配置文件中采用视图控制器标签设置请求路径和视图名,代替该控制器方法
🔑使用
① 先在Spring MVC配置文件,即springmvc.xml中添加<mvc:view-controller>
标签,在标签设置请求路径和视图名
<mvc:view-controller path="/testView01" view-name="success"></mvc:view-controller>
🔺
<mvc:view-controller>
标签的两个属性
- path:设置请求路径,对应控制器方法中
@RequestMapping
注解的value
属性值- view-name:设置视图名称,对应控制器方法的返回值
② 再添加一个<mvc:annotation-driven/>
标签,用于开启注解驱动,必须要开启
<mvc:annotation-driven/>
❓ 为什么要开启注解驱动:在springmvc.xml中添加了任何一个控制器方法对应的视图控制器标签后,所有其他的控制器(或者说控制器方法)的请求映射将自动失效,即Spring MVC匹配不到所有的控制器方法,此时必须在springmvc.xml(核心配置文件)中添加开启注解驱动的标签,该标签以后经常用到,所以一般springmvc.xml中都需要开启注解驱动
二、RESTFul
2.1 简介
💬概述
- RESTFul中的REST --> Representational State Transfer,意思是表现层资源状态转移
- RESTFul是一种资源操作、资源定位的风格,是基于REST搭建的API,不是一个标准、也不是一种协议,仅仅是一种风格
🔑Http动词设计
1. GET --> get请求,对应查询操作(Read)
2. POST --> post请求,对应添加操作(Create)
3. PUT --> put请求,对应修改操作(Update)
4. DELETE --> delete请求,对应删除操作(Delete)
🔑REST风格的路径设计:RESTFul风格提倡路径(URL地址)采用统一的风格设计,即从前到后每个单词用斜杠/
分开,不使用?
加键值对的方式添加请求参数,而是将请求参数作为路径的一部分,同样使用/
分开,以保证整体风格的统一。四个基本操作CRUD、Http动词、传统路径设计以及RESTFul风格的路径设计关系表如下
基本操作(CRUD) | Http动词 | 传统路径设计 | RESTFul路径设计 | 描述 |
---|---|---|---|---|
查询操作 | GET | /getUserById?id=1 | /user/1 | 查询id为1的用户信息 |
添加操作 | POST | /addUserInfo | /user | 添加一条用户信息 |
修改操作 | PUT | /updateUserInfo | /user | 修改用户信息 |
删除操作 | DELETE | /deleteUserById?id=1 | /user/1 | 删除id为1的用户信息 |
💡 Spring MVC学习 | @RequestMapping注解对REST风格的路径书写有过简单介绍
💡 RESTFul风格的路径设计中,添加操作和修改操作、查询操作和删除操作的路径分别是一样的,但因为它们的请求方式,即对应的Http动词不同,所以实际处理也会不同,这也是为什么RESTFul风格能保持统一的原因
2.2 PUT和DELETE请求的实现
2.2.1 HiddenHttpMethodFilter过滤器
🔍引入问题:在RESTFul风格下实现修改和删除操作需要使用PUT和DELETE请求方式,但当前浏览器只支持GET和POST请求,不支持PUT和DELETE请求,那么该如何解决?
🔑问题解决:使用Spring MVC提供的内部过滤器——HiddenHttpMethodFilter
🔑HiddenHttpMethodFilter介绍
-
功能:可以将POST请求转变成PUT或DELETE请求(还能转变成PATH请求)
-
使用条件
① 在web.xml文件中配置
<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>
💡 在web.xml中配置
HiddenHttpMethodFilter
过滤器时,必须配置在CharacterEncodingFilter
编码过滤器后面,不然设置编码的操作会失效(具体原因查看前面)② 当前请求必须设置为POST请求
③ 当前请求必须带上一个请求参数,请求参数名必须为
_method
,而请求参数值设置为真正需要的请求方法(PUT、DELETE)
💡 当满足上述后面两个条件时,
HiddenHttpMethodFilter
过滤器会将当前请求方式POST转变为请求参数_method
的参数值,因此_method
的值才是最终的请求方式,也就是我们想要设置的请求方式(PUT、DELETE),具体怎么转变参见源码分析
🔑分析HiddenHttpMethodFilter源码
① 对于过滤器,最重要的方法就是执行过滤的方法,在HiddenHttpMethodFilter
源码中可以找到doFilterInternal()
方法,该方法就是执行过滤的方法(与CharacterEncodingFilter
的执行过滤方法类似)
② 进入doFilterInternal()
方法中,可以看到过滤器先通过request.getMethod()
方法获取当前请求的请求方式,判断当前请求方法是否为POST,如果是POST才继续操作,所以使用HiddenHttpMethodFilter
过滤器的条件之一是当前请求必须设置为POST
③ 在当前请求方式是POST的情况下,再获取当前请求中的请求参数名为this.methodParam
的请求参数值,点击this.methodParam
可以看到,该请求参数其实就是_method
,即我们在表单中添加的隐藏域表单项(或者说请求参数),表单项名就是_method
,所以这也是使用HiddenHttpMethodFilter
的条件之一。再获取到_method
的参数值之后,在有长度(不为空)的情况下将其转成大写字母并赋值给method
,此时的method
值就是我们想要设置的请求方法(PUT、DELETE)
④ 最后我们看到一个ALLOWED_METHODS
变量,点击该变量,可以看到它就是一个List集合,集合中有三个固定常量参数,分别为PUT、DELETE和PATCH,当该集合包含method
时,就创建HiddenHttpMethodFilter
中一个内部类对象requestToUse
,该内部类为HttpMethodRequestWrapper
,该内部类的所用就是将当前请求对象中的请求方式换成method
值对应的请求方式(即PUT、DELETE、PATCH中的一个),创建出来的对象就是更新请求方式后的请求对象
⑤ 进入内部类HttpMethodRequestWrapper
中,可以看到类中将当前请求对象的getMethod()
方法进行重写,返回新的请求方式,该新的请求方式就是doFilterInternal()
方法中传入的method
值,从而实现请求对象中请求方式的更新
2.2.2 实现PUT请求
① 在web.xml文件中配置hiddenHttpMethodFilter
过滤器
② 创建测试控制器方法,方法的@RequestMapping
注解添加method
属性,属性值设置为RequestMethod.PUT
,并通过方法形参获取请求参数
@RequestMapping(
value = "/user",
method = RequestMethod.PUT
)
public String updateUserInfo(String username, String password) {
System.out.println("PUT --> 修改用户信息");
System.out.println("用户名 --> " + username);
System.out.println("密码 --> " + password);
return "success";
}
③ 创建测试表单,<form>
标签中的method
属性值设置为post
,即表单的请求方式设置为post
,然后在表单内添加一个隐藏域,隐藏域为表单项之一,name
属性设置为_method
,value
设置为put
(大写也可以)
<form th:action="@{/user}" method="post">
<!-- 添加隐藏域 -->
<input type="hidden" name="_method" value="put">
用户名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
<input type="submit" value="修改信息">
</form>
❓ 为什么添加隐藏域:因为使用
HiddenHttpMethodFilter
过滤器的前提是需要添加请求参数_method
,表单中每一个表单项就对应一个请求参数,所以需要添加一个表单项来传输_method
参数,而该请求参数即该表单项与用户无关,无需展示到页面,因此需要添加隐藏域
2.2.3 实现DELETE请求
🔺 删除功能的实现思路:删除功能的实现同样需要将请求方式设置为POST请求,然后添加隐藏域带上请求参数
_method
,参数值设置为delete
,但POST请求和隐藏域只能在表单元素中添加,而删除功能一般只用一个简单的超链接实现,然而超链接的请求方式是GET,所以实现删除功能需要创建删除的超链接和对应的提交表单,然后使用JS(这里采用vue来实现)给超链接绑定单击事件,当点击超链接时把链接赋值给表单的action
属性,最后再提交
① 在web.xml文件中配置HiidenHttpMethodFilter
过滤器
② 创建控制器方法,方法的@RequestMapping
注解添加method
,属性值设置为RequestMethod.DELETE
,value
属性值中采用占位符代替请求参数id,并添加请求参数id对应的形参,形参上添加@PathVariable
注解,方法返回值需要设置为重定向视图对应的视图名称(一般增删改操作之后需要使用重定向回到首页)
@RequestMapping(
value = "/user/{id}",
method = RequestMethod.DELETE
)
public String deleteUserById(@PathVariable("id") String id) {
System.out.println("删除用户信息 --> DELETE");
System.out.println("被删除用户的id --> " + id);
return "redirect:/success";
}
③ 在test-user.html中先创建一个删除超链接,超链接的href
属性中需要带上一个请求参数id值,然后在<a>
标签中添加id
和@click
属性,用于绑定单击事件
<a id="delAId" @click="deleteUser" th:href="@{/user/2}">删除用户信息</a>
❓ 如果请求参数需要动态获取:这里演示的id请求参数是写死的,项目中是需要动态获取,如果需要从域对象中获取,则需要将前面的路径与获取参数的表达式(
${内置对象.域对象中的数据名}
)拼接起来,有两种拼接方式,比如从request域中获取user对象,再获取user对象的id值
- 将表达式直接拼接在
@{}
中,此时需要将前面的路径写到单引号''
里面:th:href="@{'/user/' + ${user.id}"
- 将表达式拼接在
@{}
后面:th:href="@{/user} + ${user.id}"
④ 再添加一个表单,表单元素中无需添加action
属性,只需设置method
属性,并设置为post
,然后在表单中添加一个隐藏域,隐藏域中设置表单项名_method
(请求参数名),值设置为delete
,无需添加其他表单项,也无需添加提交按键
<form id="delFormId" method="post">
<input type="hidden" name="_method" value="delete">
</form>
⑤ 引入vue.js文件,放到webapp/static/js目录下,然后在test-user.html中通过<script>
标签引入文件,然后再使用vue操作超链接和表单两个元素,处理点击事件
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
<script type="text/javascript">
// 使用vue处理点击事件
let vue = new Vue({
el:"#delAId",
methods:{
// event表示当前事件
deleteUser:function (event) {
// 通过id获取表单元素
let delForm = document.getElementById("delFormId");
// 将超链接的href属性值赋值给表单的action属性
delForm.action = event.target.href;
// 提交表单
delForm.submit();
// 阻止超链接的默认跳转行为
event.preventDefault();
}
}
});
</script>
⑥ 在工程中添加的静态资源,一般不会自动加载(即不会出现在maven工程的target目录下),所以需要先手动重新打包,即执行maven的package命令,然后在springmvc.xml文件中开放对静态资源的访问(使用默认的servlet来处理静态资源)
<!-- 开放对静态资源的访问 -->
<mvc:default-servlet-handler/>
❓ 关于静态资源的访问
- 在Spring MVC中,浏览器发送的请求包括一些静态资源首先统一交给前端控制器
DispatcherServlet
处理,而前端控制器会直接将静态资源当作普通的请求路径来处理,由于找不到静态资源所对应的映射关系(或者说找不到静态资源对应的控制器方法),Spring MVC会认为找不到对应资源,因此没有在springmvc.xml中配置<mvc:default-servlet-handler/>
标签的情况下,点击删除链接后页面就会报404错误- 既然前端控制器无法识别静态资源并处理,就需要将静态资源交给tomcat服务器中默认的servlet
DefaultServlet
处理,而使用默认servlet就必须在springmvc.xml中开放对静态资源的访问- 在springmvc.xml中必须同时配置开放静态资源的访问和开启注解驱动,即
<mvc:default-servlet-handler/>
和<mvc:annotation-driven/>
两个标签必须同时加上,如果只加前者,则所有请求都交给默认servlet处理,此时就只有静态资源能被处理,其它请求资源都无法被默认servlet处理;如果只加后者,就无法处理静态资源,因此必须两个都加上,缺一不可❓ 关于服务默认的servlet
- 服务器默认的servlet——
DefaultServlet
配置在tomcat的全局配置文件web.xml中,配置的资源路径跟前端控制器一样,都是为/
- 因为tomcat中的web.xml文件是部署到全部tomcat工程,而当前工程中的web.xml仅部署到当前工程,如果两个web.xml文件中的配置发生冲突时,如
DefaultServlet
和DispatcherServlet
的资源路径都是/
,会以当前工程中的web.xml为准(就近原则),所以浏览器发起的请求会先经过DispatcherServlet
,而不是先经过DefaultServlet