基于尚硅谷 B 站视频 BV1Ry4y1574R 配套笔记改写
文章目录
基本使用
配置方式
创建 maven 项目,在 pom.xml 文件中引入相关依赖。
<!-- SpringMVC -->
<!--
https://mvnrepository.com/artifact/org.springframework/spring-webmvc
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.21</version>
</dependency>
<!-- Spring5 和 Thymeleaf 的整合包 -->
<!--
https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf-spring5
-->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.15.RELEASE</version>
</dependency>
在 resources 目录下创建 Spring 配置文件。
在 web.xml 文件中配置前端控制器 DispatcherServlet 和 Spring 配置文件。
根据 Servlet 生命周期,第一次请求时会访问 Servlet 的 init 初始化方法。
类路径 classpath
指的是编译后路径:本项目[或模块目录]/target/[项目或模块名]/WEB-INF/classes
项目中 java 目录下的文件和 recources 目录下的文件都在类路径下。
SpringMVC 基于原生的 Servlet,通过功能强大的前端控制器 DispatcherServlet,对请求和响应进行统一处理。
标签中使用 / 和 /* 的区别
/ 所匹配的可以是 /login 或 .html 或 .js 或 .css 的请求路径,但是 / 不能匹配 .jsp 的请求路径。这样就可以避免在访问 jsp 页面时,该请求被 DispatcherServlet 处理,从而找不到相应的页面。/* 能够匹配所有请求路径,例如在使用 filter 过滤器时,若需要对所有请求进行过滤,就使用 /* 写法。
<!-- 配置 SpringMVC 的前端控制器,对浏览器发送的请求统一进行处理 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name> <!-- 一般为类名首字母小写 -->
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<!-- 通过初始化参数指定 SpringMVC 配置文件的位置和名称 -->
<init-param>
<!-- contextConfigLocation 为固定值 -->
<param-name>contextConfigLocation</param-name>
<!-- 使用 classpath: 表示从类路径查找配置文件
例如 maven 工程中的 src/main/resources -->
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<!--
作为框架的核心组件,在启动过程中有大量的初始化操作要做,
而这些操作放在第一次请求时才执行会严重影响访问速度。
因此需要通过此标签将 DispatcherServlet 的初始化时间提前到服务器启动时。
当值为 0 或者大于 0 时,代表启动时加载该 Servlet。正数的值越小,
启动时加载该 Servlet 的优先级越高。
如果为负数,则启动时不会加载该 Servlet,只有该 Servlet 被选择时才会加载。
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--
设置 SpringMVC 的核心控制器所能处理的请求路径
/ 所匹配的可以是 /login 或 .html 或 .js 或 .css 方式的请求路径
但是 / 不能匹配 .jsp 的请求路径
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
在 Spring 配置文件中开启自动扫描组件,配置 Thymeleaf 视图解析器。
添加 context 命名空间
在文件开头的 标签中添加以下内容:
xmlns:context="http://www.springframework.org/schema/context" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 加入扫描组件用的 context 命名空间 -->
<!-- 自动扫描组件 -->
<context:component-scan base-package="com.insight.mvc.controller"/>
<!-- 配置 Thymeleaf 视图解析器 -->
<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>
<!-- 以下两个标签一起用 -->
<!-- 开启 mvc 注解驱动 -->
<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>
<!--
处理静态资源,例如 html、js、css、jpg
若只设置该标签,只能访问静态资源,其他请求则无法访问
此时必须设置 <mvc:annotation-driven/> 解决问题
-->
<mvc:default-servlet-handler/>
</beans>
访问页面
前端控制器对浏览器发送的请求进行统一的处理,但是具体的请求有不同的处理过程,因此需要创建处理具体请求的类,即创建请求控制器。请求控制器中创建的处理请求的方法就是控制器方法。
SpringMVC 的请求控制器是类,需要通过 @Controller 注解将该类标识为一个控制层组件,交给 Spring 的 IOC 容器管理,SpringMVC 才能识别控制器的存在。
@Controller
public class DemoController // 请求控制器
{
// @RequestMapping 注解:处理请求和控制器方法之间的映射关系
// @RequestMapping 注解的 value 属性可以通过请求地址匹配请求(属性值不能重复)
// / 表示的当前项目的上下文路径 localhost:9372/mvc
// 只对 value 属性赋值可以省略不写
// @RequestMapping(value = "/")
@RequestMapping("/") // 访问 index.html 主页
public String index()
{
return "index"; // 返回视图名称
// 视图名称由 Thymeleaf 添加视图前缀和试图后缀
// /webapp/WEB-INF/templates/index.html
// 最终地址为 localhost:9372/mvc/webapp/WEB-INF/templates/index.html
}
@RequestMapping("/target") // 访问 target.html 目标页
public String notTarget() // 与方法名无关
{
return "target";
}
}
在 WEB-INF 目录下创建并编写 index.html 首页和 target.html 目标页,其中首页需要使用Thymeleaf,目标页为普通页面。
浏览器可以访问到 WEB-INF 目录下,说明这一过程是请求转发而不是请求重定向。请求转发过程是一次请求,转发后访问的地址没有变化,不可以访问项目以外的资源。
<!DOCTYPE html>
<!-- 加入 Thymeleaf 所用的命名空间 -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<h1>首页</h1>
<!--
服务器解析的 / 是应用程序上下文 http://localhost:9372/mvc
浏览器解析的 / 是 http://localhost:9372
下面的配置是错误的:
<a href="/target">访问目标页面 target.html</a>
下面的配置是对的:
<a href="/mvc/target">访问目标页面 target.html</a>
但应用程序上下文一旦改动就是错的
-->
<!--
超链接将请求转发到目标页
Thymeleaf 用法配置绝对路径,自动添加应用程序上下文
-->
<a th:href="@{/target}">访问目标页面 target.html</a>
</body>
</html>
配置并开启 Tomcat 服务器,自动访问首页,再通过点击主页的超链接来访问目标页。
过程总结
通过浏览器发送请求给服务器,若请求地址符合前端控制器 DispatcherServlet 在 web.xml 文件中的 标签内容,请求就会被前端控制器处理。
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!-- 设置 SpringMVC 的前端控制器所能处理的请求路径 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
前端控制器会读取 Spring 配置文件,通过自动扫描组件找到请求控制器,将用户请求地址和请求控制器中 @RequestMapping 注解的 value 属性值进行匹配,若匹配成功,该注解所标识的控制器方法就是处理请求的方法。
<!-- 加入扫描组件用的 context 命名空间 -->
<!-- 自动扫描组件 -->
<context:component-scan base-package="com.insight.mvc.controller"/>
<!-- 配置 Thymeleaf 视图解析器 -->
@RequestMapping("/target") // 访问 target.html 目标页
public String notTarget()
{
return "target"; // 视图名称
}
控制器方法需要返回的视图名称会被 Thymeleaf 视图解析器解析,加上前缀和后缀后组成视图的请求路径。Thymeleaf 对视图进行渲染后在浏览器展示,最终请求转发到了视图所对应页面。
<!-- 视图前缀 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀 -->
<property name="suffix" value=".html"/>
@RequestMapping 注解
使用位置
@RequestMapping 注解可以用在请求控制器或控制器方法上。用在请求控制器上也必须用在控制器方法上,作用是给控制器方法注解的 value 属性值前加上前缀,增加请求路径的地址层级。
@Controller
@RequestMapping("/class")
public class RequestMappingController
{
// 请求路径 /class/method
// Thymeleaf 用法配置绝对路径,自动添加应用程序上下文:
// <a th:href="@{/class/method}">
// 测试 @RequestMapping 的位置
// </a>
@RequestMapping("/method")
public String success()
{
return "success";
}
value 属性
设置 @RequestMapping 注解的 value 属性可以明确请求的请求地址。这一属性是字符串数组,表示一个注解可以通过映射匹配多个请求地址,但请求地址不能重复,否则会报错。
@Controller
@RequestMapping("/class")
public class RequestMappingController
{
// 一个注解匹配两个请求地址
@RequestMapping(value = {"method", "Method"})
public String success()
{
return "success";
}
}
模糊匹配路径
SpringMVC 支持模糊匹配的请求路径,有三种请求路径通配符。
通配符 | 示例 | 说明 |
---|---|---|
? | /ant/p?ttern | 匹配单字符(不包括部分特殊字符) |
* | /ant/*/path | 匹配 0 或者任意数量的字符** |
** | /ant/**/path | 匹配 0 或者更多的目录 |
- /ant/*.html 匹配所有在 ant 路径下的 .html文件。
- 在使用 ** 时,/**/ 的形式是固定的,不能更改。
路径中的占位符(@PathVariable 注解)
SpringMVC 支持使用路径中的占位符 {}。当浏览器把某些数据通过路径的方式发送给控制器方法时,在相应方法 @RequestMapping 注解的 value 属性中通过路径占位符表示传输的数据名称,再通过==@PathVariable 注解,将占位符所表示的数据赋值给控制器方法的形参。==
<!-- 原来的路径写法 /class/testPath?id=1&username=admin -->
<!-- 使用占位符的路径写法 -->
<a th:href="@{/class/testPath/1/admin}">
测试路径中的占位符
</a>
@RequestMapping("/testPath/{id}/{username}")
public String testPath
// 注解将占位符 { } 所表示的数据赋值给控制器方法的形参
(@PathVariable("id") Integer id,
@PathVariable("username") String username)
{
System.out.println("id = " + id + " username = " + username);
return "success";
}
method 属性
@RequestMapping 注解的 value 属性用于设置支持的请求方式。这一属性是枚举的常量数组,表示一个注解可以支持多种请求方式。请求地址符合注解的 value 属性值,并且请求方式符合 method 属性值,请求才能成功。如果不设置 method 属性的值,则默认支持 get 和 post 请求方式。
@RequestMapping(value = "/method",
// 一个注解可以支持多种请求方式(默认设置,可以不写)
method = {RequestMethod.GET, RequestMethod.POST})
public String success()
{
return "success";
}
对于处理指定请求方式的控制器方法,SpringMVC 中提供了 @RequestMapping 的派生注解,其作用相当于添加了一个 method 属性的值。
// @RequestMapping 的派生注解
@GetMapping("/testGetMapping")
public String testGetMapping()
{
return "success";
}
浏览器支持发送的请求方式有 get 和 post。若在 form 表单提交时,为表单的 method 属性设置了其他请求方式的属性值,则按默认请求方式 get 处理。
<!-- 请求方式设置为 put,但按按默认请求方式 get 方式发送请求 -->
<form th:action="@{/class/testPut}" method="put">
<input type="submit"
value="测试 form 表单是否能发送的 put 方式的请求"/>
</form>
// 使用该控制器方法会报错,提示不支持 get 请求方式
@RequestMapping(value = "/testPut", method = RequestMethod.PUT)
public String testPut()
{
return "success";
}
params 属性
@RequestMapping 注解的 params 属性用于限定请求的参数。这一属性是字符串数组,一个注解可以限定多个请求参数,参数符合所有 params 属性值的限定,请求才能成功。
限定请求参数的四种表达式及含义
表达式 含义 param 限定请求必须带有 param 参数 param 限定请求必须不带 param 参数 param=value 限定请求必须带有 param 参数且参数值为 value param!=value 限定请求必须带有 param 参数但参数值不为 value
<!-- 使用 Thymeleaf 和以下传参方式,IDEA 会报错,但能正常运行 -->
<!-- <a th:href="@{/class/testParamsAndHeaders?username=admin&password=123456)}"> -->
<!-- 测试 params 属性 -->
<!-- </a> -->
<!-- Thymeleaf 的传参方式,解析后与上面的传参方式地址相同 -->
<a th:href="@{/class/testParamsAndHeaders(username='admin', password=123)}">
测试 params 属性
</a>
// 使用该控制器方法失败
@RequestMapping(value = "/testParamsAndHeaders",
params = {"username", "password!=123"}) // 同时限定两个参数
public String testParamsAndHeaders()
{
return "success";
}
headers 属性
@RequestMapping 注解的 headers 属性用于限定请求头的信息,用法与 params 属性相似,也有限定信息的四种表达式。这一属性是字符串数组,一个注解可以限定多个请求头信息,请偷偷信息符合所有 headers 属性值的限定,请求才能成功。
@RequestMapping(value = "/testParamsAndHeaders",
params = {"username", "password=123"},
headers = {"Host=localhost:9372"}) // 限定请求头信息
public String testParamsAndHeaders()
{
return "success";
}
查看请求头信息
在浏览器按 F12 进入开发者界面,然后访问网页,再选择网络下的标头,找到请求标头。
获取请求参数
ServletAPI 获取
将 HttpServletRequest 类对象作为控制器方法的形参,此时该形参表示封装了当前请求报文的对象。通过 ServletAPI 获取请求参数的方式是原生的,已经通过 SpringMVC 封装,不建议使用。
<a th:href="@{/testServletAPI(username='admin', password=123)}">
测试使用 Servlet API 获取请求参数
</a><br/>
@RequestMapping("/testServletAPI")
// 形参位置的 request 表示当前请求
public String testServletAPI(HttpServletRequest request)
{
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println("username = " + username
+ " password = " + password);
return "success";
}
控制器方法的形参获取
控制器方法中设置和请求参数同名的形参,当浏览器发送请求,使用该控制器方法时,请求参数会被赋值给相应的形参。
多个同名的请求参数
可以在控制器方法的形参中设置字符串数组或者字符串的形参接收此请求参数。
- 若使用字符串数组的形参,此参数的数组中包含每一个数据。
- 若使用字符串的形参,此参数的值为数据间使用逗号拼接的结果。
<form th:action="@{/testParam}" method="get">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
爱好:<!-- 多个同名的请求参数 -->
<input type="checkbox" name="hobby" value="java">java
<input type="checkbox" name="hobby" value="c++"/>c++
<input type="checkbox" name="hobby" value="js"/>js
<br/>
<input type="submit" value="测试使用控制器的形参获取请求参数"/>
</form>
@RequestMapping("/testParam")
public String testParam
// 与请求参数同名的形参
(String username, String password,
String[] hobby) // 可以用 String hobby,输出 java,c++,js
{
System.out.println("username = " + username
+ " password = " + password
+ " hobby = " + Arrays.toString(hobby));
return "success";
}
@RequestParam 注解
@RequestParam 注解用于在请求参数和控制器方法的形参件建立映射关系。
属性 | 作用 |
---|---|
value 或 name | 指定为形参赋值的请求参数的参数名。 |
defaultValue | 当 value 属性值所指定的请求参数没有传输或传输的值为 “” 时,则使用该属性值作为默认值为形参赋值。 |
required | 设置是否必须传输此请求参数,默认值为 true。 |
- required 属性值为 true 时,若没有传输该请求参数,且没有设置 defaultValue 属性值,则页面报错。
- required 属性值为 false 时,若没有传输 value 属性值所指定的请求参数,则注解所标识的形参的值为 null。
用户名:<input type="text" name="user_name"/><br/> <!-- 请求参数与方法名不同 -->
@RequestMapping("/testParam")
public String testParam
// 形参与请求参数建立映射关系,且不一定传输请求参数值
(@RequestParam(value = "user_name", required = false,
defaultValue = "default") // 形参默认值
String username,
String password, String[] hobby)
@RequestHeader 注解 @CookieValue 注解
@RequestHeader 注解用于在请求参数和控制器方法的形参间建立映射关系。
@CookieValue 注解用于在 cookie 数据和控制器方法的形参间映射关系。
这两个注解的用法与 @RequestParam 注解相似。
@RequestMapping("/testParam")
public String testParam
(@RequestHeader(defaultValue = "localhost:8080") String host,
@CookieValue(value = "Idea-ab16e683") String idea, // IDEA 的 cookie
// session 的 cookie,不创建 session 是没有的
@CookieValue(value = "JSESSIONID", required = false) String session)
{
System.out.println("host = " + host
+ " idea = " + idea
+ " session = " + session);
return "success";
}
JavaBean 对象获取请求参数
控制器方法的形参中设置 JavaBean 的形参,当浏览器传输请求参数的参数名和 JavaBean 中的属性名相同时,请求参数会为属性赋值。
<!-- 请求参数的参数名和 JavaBean 中的属性名相同 -->
<form th:action="@{/testBean}" 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" value="测试使用实体类获取请求参数">
</form>
@RequestMapping("/testBean")
public String testBean(User user) // 请求参数会为 user 的属性赋值
{
System.out.println(user);
return "success";
}
解决请求参数的乱码问题
解决请求参数的乱码问题,可以使用 SpringMVC 提供的编码过滤器 CharacterEncodingFilter,但要在 web.xml 文件中进行配置,且一定要配置在其他过滤器之前,否则无效。
<!-- 配置 springMVC 的编码过滤器(配置在其他过滤器之前) -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- 设置编码为 UTF-8 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<!-- 开启响应编码(如果不设置,那么只有请求开启编码) -->
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
用域对象共享数据
ServletAPI 用法
将 HttpServletRequest 接口引用的对象作为控制器方法的形参,可以向域对象共享数据。直接使用 ServletAPI 的用法是原生的,已经通过 SpringMVC 封装,不建议使用。
<a th:href="@{/testRequestByServletAPI}">
用 ServletAPI 向 request 域对象共享数据
</a>
// 用 ServletAPI 向 request 域对象共享数据
@RequestMapping("/testRequestByServletAPI")
public String testRequestByServletAPI(HttpServletRequest request)
{
request.setAttribute("testRequestScope", "ServletAPI");
return "success";
}
<!-- Thymeleaf 获取 request 域对象数据用法 -->
<p th:text="${testRequestScope}"></p>
ModelAndView 用法
可以在控制器方法中创建 ModelAndView 类对象,并使用其方法来向域对象共享数据,设置视图名称。必须使控制器方法返回 ModelAndView 类对象。
一般情况下在形参中添加 ModelAndView 并使其返回也能达到效果,但有些时候不行(如异常处理器类中)。
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView()
{
ModelAndView modelAndView = new ModelAndView();
// 向请求域 request 共享数据
modelAndView.addObject("testModelAndView", "ModelAndView");
// 设置视图名称
modelAndView.setViewName("success");
return modelAndView; // 返回 ModelAndView 类对象
}
Map、Model、ModelMap 等用法
可以在控制器方法中添加 Map、Model、ModelMap 等与 BindingAwareModelMap 类有继承或实现关系的形参,并使用相关方法来向域对象共享数据。必须使控制器方法返回视图名称。
@RequestMapping("/testMap")
public String testMap(Map<String, Object> map)
{
// 向请求域 request 共享数据
map.put("testRequestScope", "Map");
// 输出对象的运行时类型
System.out.println("map = " + map.getClass().getName());
return "success"; // 返回视图名称
}
@RequestMapping("/testModel")
public String testModel(Model model)
{
// 向请求域 request 共享数据
model.addAttribute("testRequestScope", "Model");
System.out.println("model = " + model.getClass().getName());
return "success";
}
@RequestMapping("/testModelMap")
public String testModelMap(ModelMap modelMap)
{
modelMap.addAttribute("testRequestScope", "ModelMap");
System.out.println("modelMap = " + modelMap.getClass().getName());
return "success";
}
- 无论使用哪种方式,源码都使用了 ModelAndView 类对象进行封装。
- 这些形参引用对象的运行时类型都是 BindingAwareModelMap 类。
向 session 域或 Application 域共享数据
向 session 域或 Application 域共享数据可通过 SpringMVC 的注解实现,但不好用。
用 ServletAPI 的原生用法,将 HttpSession 接口引用的对象作为控制器方法的形参,可以向域对象共享数据。其中 Application 域对象借助 Session 域对象获取。
<!-- Thymeleaf 获取 request 域对象数据用法 -->
<p th:text="${testRequestScope}"></p>
<!-- Thymeleaf 获取 session 域对象数据用法 -->
<p th:text="${session.testSessionScope}"></p>
<!-- Thymeleaf 获取 application 域对象数据用法 -->
<p th:text="${application.testApplicationScope}"></p>
// HttpSession 接口引用的对象作为控制器方法的形参
@RequestMapping("/testSession")
public String testSession(HttpSession session)
{
// 向 session 域共享数据
session.setAttribute("testSessionScope", "Session");
return "success"; // 返回视图名
}
@RequestMapping("/testApplication")
public String testApplication(HttpSession session)
{
// Application 域对象借助 Session 域对象获取
ServletContext servletContext = session.getServletContext();
// 向 Application 域保存数据
servletContext.setAttribute("testApplicationScope", "Application");
return "success";
}
视图
ThymeleafView 视图
控制器方法设置的视图名称没有前缀时,使用的是 ThymeleafView。视图解析器会解析视图名称,将视图名称拼接上视图前缀和视图后缀而得到请求路径,再通过请求转发的方式实现页面跳转。
@RequestMapping("/testThymeleafView")
public String testThymeleafView()
{
return "success"; // 视图名称没有前缀
}
InternalResourceView 转发视图
当控制器方法设置的视图名以 forward: 为前缀时,创建 InternalResourceView 转发视图。此时的视图名称不会被视图解析器解析,而是前缀 forward: 被去掉,剩余部分作为请求路径的后缀,再通过请求转发的方式实现页面跳转。
当项目引入 JSTL 的依赖时,转发视图会自动转换为 JstlView 视图。
@RequestMapping("/testThymeleafView")
public String testThymeleafView()
{
return "success"; // ThymeleafView 视图
}
@RequestMapping("/testForward")
public String testForward()
{
// InternalResourceView 转发视图
return "forward:/testThymeleafView"; // 请求转发至另一个控制器方法
}
RedirectView 重定向视图
当控制器方法设置的视图名以 redirect: 为前缀时,创建 RedirectView 重定向视图。此时的视图名称不会被视图解析器解析,而是前缀 redirect: 被去掉,剩余部分作为请求路径的后缀,再通过转发的请求重定向方式实现页面跳转。
@RequestMapping("/testThymeleafView")
public String testThymeleafView()
{
return "success"; // ThymeleafView 视图
}
@RequestMapping("/testRedirect")
public String testRedirect()
{
// RedirectView 重定向视图
return "redirect:/testThymeleafView"; // 重定向至另一个控制器方法
}
视图控制器 view-controller
若控制器方法仅用来实现页面跳转,即只需要设置视图名称时,可以删除该控制器方法,在 SpringMVC 配置文件中用 view-controller 标签进行实现相同的功能。
当 SpringMVC 配置文件中设置 view-controller 标签时,控制器的请求映射将全部失效。在 SpringMVC 的配置文件中设置开启 mvc 注解驱动的标签,才能恢复控制器的功能。
<!-- 视图控制器 -->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
<!-- 开启 mvc 的注解驱动 -->
<mvc:annotation-driven/>
视图解析器 InternalResourceViewResolver(使用 jsp 页面)
如果使用 jsp 页面,不使用 Thymeleaf,则需要更改 SpringMVC 配置文件中中视图解析器的配置,使用 InternalResourceViewResolver 替代 ThymeleafViewResolver。只替换了视图解析器,关于视图的其他用法不变。
<!-- 配置视图解析器(jsp 使用)-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/templates/"/>
<property name="suffix" value=".jsp"/>
</bean>
RESTFul 风格
URL 地址
RESTFul 风格 URL 地址的各个层级间用斜杠分开,不使用问号加键值对的方式发送请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性。
操作 | 原生方式 URL | RESTFul 风格 URL | RESTFul 风格的请求方式 |
---|---|---|---|
查询操作 | getUserById?id=1 | user/1 | get 请求方式 |
保存操作 | saveUser | user | post 请求方式 |
删除操作 | deleteUser?id=1 | user/1 | delete 请求方式 |
更新操作 | updateUser | user | put 请求方式 |
/*
模拟 RESTFul 风格的用户资源增删改查
/user GET 查询所有用户信息
/user/1 GET 根据用户 id 查询用户信息
/user POST 添加用户信息
(浏览器无法直接发送 delete 和 put 请求)
*/
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String getUsers()
{
System.out.println("查询所有用户信息");
return "success";
}
@GetMapping("/user/{id}")
public String getUserById()
{
System.out.println("根据 id 查询用户信息");
return "success";
}
@PostMapping("/user")
public String addUser(String username, String password)
{
System.out.println("添加用户信息:"
+ "username = " + username
+ " password = " + password);
return "success";
}
HiddenHttpMethodFilter 发送 put 和 delete 请求
浏览器只支持发送 get 和 post 请求,但通过 SpringMVC 提供的 HiddenHttpMethodFilter 可以把 post 请求转换为 delete 或 put 请求。 在 web.xml 文件中配置该过滤器就可以使用,注意其映射必须配置在 CharacterEncodingFilter 的映射之后,否则可能会有乱码。
HiddenHttpMethodFilter 转换到 put 和 delete 请求的条件:
- 请求方式必须设置为 post。
- 请求必须传输请求参数 _method,参数值为 put 或 delete。
<!-- 配置 HiddenHttpMethodFilter -->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<!-- 编码过滤器的映射必须在其他过滤器映射之前 -->
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<form th:action="@{/user}" method="post"> <!-- 必须设置为 post -->
<input type="hidden" name="_method" value="put"> <!-- 必须传输请求参数 _method -->
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" value="修改"/><br/>
</form>
SpringMVC Thymeleaf Vue RESTFul 风格案例
准备 JavaBean DAO
完成 SpringMVC 基本使用需要的配置后,准备案例的 JavaBean 和 DAO。
package com.insight.mvc.bean;
public class Employee
{
private Integer id;
private String lastName;
private String email;
// 1 male, 0 female
private Integer gender;
public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this.id = id;
}
public String getLastName()
{
return lastName;
}
public void setLastName(String lastName)
{
this.lastName = lastName;
}
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
public Integer getGender()
{
return gender;
}
public void setGender(Integer gender)
{
this.gender = gender;
}
public Employee(Integer id, String lastName, String email, Integer
gender)
{
super();
this.id = id;
this.lastName = lastName;
this.email = email;
this.gender = gender;
}
public Employee()
{
}
}
package com.insight.mvc.dao;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import com.insight.mvc.bean.Employee;
import org.springframework.stereotype.Repository;
@Repository
public class EmployeeDao
{
private static Map<Integer, Employee> employees = null;
static // 静态代码块初始化数据,模拟数据库
{
employees = new HashMap<Integer, Employee>();
employees.put(1001, new Employee(1001, "E-AA", "aa@163.com", 1));
employees.put(1002, new Employee(1002, "E-BB", "bb@163.com", 1));
employees.put(1003, new Employee(1003, "E-CC", "cc@163.com", 0));
employees.put(1004, new Employee(1004, "E-DD", "dd@163.com", 0));
employees.put(1005, new Employee(1005, "E-EE", "ee@163.com", 1));
}
private static Integer initId = 1006;
public void save(Employee employee) // 添加或修改员工信息
{
if (employee.getId() == null) // 为员工设置 id
{
employee.setId(initId++);
}
employees.put(employee.getId(), employee);
}
public Collection<Employee> getAll() // 查询所有员工信息
{
return employees.values();
}
public Employee get(Integer id)
{
return employees.get(id);
}
public void delete(Integer id)
{
employees.remove(id);
}
}
功能清单
功能 | URL 地址 | 请求方式 |
---|---|---|
访问首页 | / | GET |
查询所有数据 | /employee | GET |
删除 | /employee/1 | DELETE |
跳转到添加数据页面 | /toAdd | GET |
保存数据 | /employee | POST |
跳转到更新数据页面 | /employee/1 | GET |
更新数据 | /employee | PUT |
访问首页
在 SpringMVC 配置文件中配置视图控制器,实现访问首页。
<!-- path="/" 默认跳转到首页 -->
<mvc:view-controller path="/" view-name="index"/>
创建首页的 html 文件。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"> <!-- 加入 Thymeleaf 命名空间 -->
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>首页</h1>
<!-- Thymeleaf 用法的动态绝对路径-->
<a th:href="@{/employee}">查看员工信息</a><br/>
</body>
</html>
查询所有数据
在控制器中使用 @Autowired 注解自动注入 employeeDao 属性,并创建查询所有数据的控制器方法,使用 employeeDao 获取数据后向请求域共享,最后跳转至数据显示页面。
@Controller
public class EmployeeController
{
@Autowired // 自动注入属性
private EmployeeDao employeeDao;
@GetMapping("/employee") // get 请求方式的映射
public ModelAndView getEmployees(ModelAndView mav)
{
Collection<Employee> employees = employeeDao.getAll();
// 向请求域 request 共享数据
mav.addObject("employees", employees);
// 设置视图名称
mav.setViewName("employees");
return mav;
}
}
创建 employees.html 文件,作为显示信息和增删改查的功能页面。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>员工信息</title>
</head>
<body>
<!-- 信息列表 -->
<table border="1" cellpadding="0" cellspacing="0"
style="text-align:center;" id="dataTable"> <!-- 设置简易的表格样式 -->
<tr>
<th colspan="5">员工信息</th> <!-- 合并 5 列 -->
</tr>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th> <!-- 添加 Employee 的超链接 -->
<th>option(<a th:href="@{/toAdd}">添加)</a></th>
</tr> <!-- Thymeleaf 用法获取域对象属性的数据 -->
<tr th:each="employee:${employees}">
<td th:text="${employee.id}"></td>
<td th:text="${employee.lastName}"></td>
<td th:text="${employee.email}"></td>
<td th:text="${employee.gender}"></td>
<td>
<a th:href="@{'/employee/'+${employee.id}}">删除</a>
<!-- Thymeleaf 与 RESTFul 风格的地址写法 -->
<!-- id 请求参数用于回显信息 -->
<a th:href="@{'/employee/'+${employee.id}}">修改</a>
</td>
</tr>
</table>
</body>
</html>
删除数据
创建处理 employees.html 文件中增加用于处理 delete 请求方式的表单。
<!-- 通过超链接控制表单的提交,将 post 请求转换为 delete 请求 -->
<form id="deleteForm" method="post">
<!-- HiddenHttpMethodFilter 要求必须传输 _method 请求参数,其值为最终的请求方式 -->
<input type="hidden" name="_method" value="delete"/>
</form>
为功能为删除的超链接绑定点击事件。
<!-- Vue 用法的单击事件 -->
<a @click="deleteEmployee"
th:href="@{'/employee/'+${employee.id}}">删除</a>
在 head 标签中 引入 vue.js,并通过 Vue 处理点击事件。
<script type="text/javascript" th:src="@{/static/vue.js}"></script>
<script type="text/javascript">
var vue = new Vue // Vue 用法
(
{
el: "#dataTable", // el 用于指定 Vue 对象要关联的 HTML 元素
methods: // methods 用于指定事件驱动的函数
{
deleteEmployee: function (event)
{
// 根据 id 触发表单元素
var deleteForm
= document.getElementById("deleteForm");
// 触发事件超链接的 href 属性赋值给表单的 action
deleteForm.action = event.target.href;
deleteForm.submit(); // 提交表单
event.preventDefault(); // 取消超链接默认行为
}
}
}
);
</script>
在控制器中增加处理删除数据的控制器方法。
@DeleteMapping("/employee/{id}") // 占位符获得参数
public ModelAndView deleteEmployee
// 获取请求路径中的参数
(@PathVariable("id") Integer id, ModelAndView mav)
{
employeeDao.delete(id);
mav.setViewName("redirect:/employee"); // 重定向至数据显示页面
return mav;
}
跳转到添加数据页面
在 SpringMVC 配置文件中配置视图控制器,仅实现跳转功能。
<!-- 配置视图控制器 -->
<mvc:view-controller path="/toAdd" view-name="employee_add"/>
创建添加数据显示的 html 页面。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>添加员工</title>
</head>
<body>
<!-- 添加数据的表单 -->
<form th:action="@{/employee}" method="post">
lastName:<input type="text" name="lastName"/><br/>
email: <input type="text" name="email"/><br/>
gender: <input type="radio" name="gender" value="1"/>male
<input type="radio" name="gender" value="0"/>female<br/>
<input type="submit" value="添加"/><br/>
</form>
</body>
</html>
保存数据
在控制器中创建用于保存对象的控制器方法。
@PostMapping("/employee") // 通过 JavaBean 类的形参获取请求参数
public ModelAndView addEmployee(Employee employee, ModelAndView mav)
{
employeeDao.save(employee);
mav.setViewName("redirect:/employee"); // 重定向至数据显示页面
return mav;
}
跳转到更新数据页面
在控制器中创建通过 id 获取对象的控制器方法。
@GetMapping("/employee/{id}")
public ModelAndView getEmployeeById // 获取 Employee 对象修改数据
(@PathVariable Integer id, ModelAndView mav)
{
Employee employee = employeeDao.get(id);
mav.addObject("employee", employee);
mav.setViewName("employee_update");
return mav;
}
创建更新数据显示的 html 页面。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>修改员工信息</title>
</head>
<body>
<form th:action="@{/employee}" method="post">
<input type="hidden"
name="id" th:value="${employee.id}"/>
<input type="hidden"
name="_method" value="put"/>
lastName:<input type="text" name="lastName"
th:value="${employee.lastName}"/><br/>
email: <input type="text" name="email"
th:value="${employee.email}"/><br/>
<!--
th:field="${employee.gender}" 可用于单选框或复选框的回显。
若单选框的 value 和 employee.gender 的值一致,
就相当于添加了 checked="checked" 属性。
-->
gender: <input type="radio" name="gender"
value="1" th:field="${employee.gender}"/>male
<input type="radio" name="gender" value="0"
th:field="${employee.gender}"/>female<br/>
<input type="submit" value="修改"/><br/>
</form>
</body>
</html>
更新数据
在控制器中创建用于更新数据的控制器方法。
@PutMapping("/employee")
public ModelAndView updateEmployee(Employee employee, ModelAndView mav)
{
employeeDao.save(employee);
mav.setViewName("redirect:/employee");
return mav;
}
HttpMessageConverter 报文信息转换
HttpMessageConverter 报文信息转换器,将请求报文转换为 Java 对象,或将 Java 对象转换为响应报文。 HttpMessageConverter 提供了两个注解和两个类型。
@RequestBody 注解
浏览器向服务器发送请求报文时,在对应的控制器方法上设置一个形参,并使用 @RequestBody 标识形参,当前请求的请求体就会被赋值给这一形参。
<form th:action="@{/testRequestBody}" method="post"> <!-- post 方式有请求体 -->
<!-- 请求参数将被写在请求体中 -->
<input type="text" name="username"/>
<input type="text" name="password"/>
<input type="submit" value="测试 @RequestBody 注解"/>
</form>
@PostMapping("/testRequestBody")
public ModelAndView testRequestBody
// 请求体数据将会被赋值给该形参
(@RequestBody String requestBody, ModelAndView mav)
{
System.out.println("requestBody = " + requestBody);
mav.setViewName("sucess");
return mav;
}
RequestEntity 类
RequestEntity 是封装请求报文的类。**在控制器方法的形参列表中设置该类型的形参,当前请求的请求报文就会赋值给该形参。**通过该类的 getHeaders() 方法获取请求头信息,通过 getBody() 方法获取请求体信息。
@PostMapping("/testRequestEntity")
public ModelAndView testRequestEntity
// 泛型指定了请求体信息的类型
(RequestEntity<String> requestEntity, ModelAndView mav)
{
HttpHeaders httpHeaders = requestEntity.getHeaders();
System.out.println("httpHeaders = " + httpHeaders);
String requestBody = requestEntity.getBody();
System.out.println("requestBody = " + requestBody);
mav.setViewName("success");
return mav;
}
@ResponseBody 注解
服务器给浏览器发送响应数据时,使用 @ResponseBody 标识对应的控制器方法,该方法的返回值直接作为响应报文的响应体发送给浏览器。也可以使用原生 ServletAPI 进行响应,但不推荐这种用法。
@GetMapping("/testResponse")
public void testResponse // 使用 ServletAPI 响应
(HttpServletResponse response) throws IOException
{
response.getWriter().write("ServletAPI Response");
}
@RequestMapping("/testResponseBody")
@ResponseBody // 标识响应的控制器方法
public String testResponseBody()
{
return "SpringMVC Response"; // 返回值就是响应体
}
响应 Java 对象转换为的 JSON 格式字符串
如果服务器直接响应给浏览器 Java 对象,浏览器无法接收。可以使用 @ResponseBody 注解将 Java 对象转换为 JSON 格式的字符串再进行响应。
使用步骤
导入 jackson 的依赖。
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.3</version> </dependency>
在 SpringMVC 的配置文件中开启注解驱动。自动装配的消息转换器会将响应到浏览器的 Java 对象转换为 Json 格式的字符串。
<!-- 开启注解驱动(要添加 mvc命名空间) --> <mvc:annotation-driven/>
在处理器方法上使用 @ResponseBody 注解标识,并将 Java 对象直接作为控制器方法的返回值返回,响应内容就会自动转换为 JSON 格式的字符串。
@RequestMapping("/testResponseUser") @ResponseBody public User testResponseUser() { return new User(1001, "admin", "123", 22, "男"); }
SpringMVC 和 Axios 处理 Ajax
开启注解驱动,否则无法使用。
<!-- 开启注解驱动(要添加 mvc命名空间) -->
<mvc:annotation-driven/>
加入 Ajax 请求的超链接,并绑定单击事件。
<!-- Thymeleaf 用法的请求地址-->
<a id="app" th:href="@{/testAxios}"
@click="testAxios">
<!-- 使用 vue 绑定单击事件-->
SpringMVC 处理 ajax
</a>
引入 Vue 和 Axios 的 js 依赖,并用它们处理单击事件。
<!-- 引入 Vue 和 Axios 的 js 依赖(在项目中添加 js 文件并重新打包后)-->
<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">
new Vue
({
el: "#app", // 控制的元素是超链接
methods: // 使用的函数
{
// 处理单击事件的函数
testAxios: function (event) // event 单击事件
{
event.preventDefault(); // 阻止标签默认行为为(超连接跳转页面)
axios // Axios 发送请求(封装了 Ajax)
({
method: "post", // 请求方式 post
// url 是单击事件目标(超链接)的请求地址
url: event.target.href,
params: // 传递的请求参数
{
username: "admin",
password: "123456"
}
}).then // 请求成功的响应
(
function (response) // 响应函数
{
alert(response.data); // 弹窗输出相应的数据
}
);
}
}
});
</script>
使用 @RequestBody 注解编写相应的控制器方法,并用控制器方法的形参获取请求参数。
@RequestMapping("/testAxios")
@ResponseBody
public String testAxios(String username, String password) // 获取请求参数
{
System.out.println("ResponseBody: username = "
+ username + " password = " + password);
return "Axios Ajax"; // 返回响应信息
}
@RestController 注解
@RestController 注解是 springMVC 提供的一个复合注解,标识在控制器的类上,就相当于为类添加了 @Controller 注解,并且为其中的每个方法添加了 @ResponseBody 注解。(同样需要开启注解驱动,建议每次使用 SpringMVC 都开启,不再赘述)
@RestController // 复合注解
public class HttpController // 控制器类
{
@RequestMapping("/testAxios")
// @ResponseBody
public String testAxios(String username, String password) // 获取请求参数
{
System.out.println("ResponseBody: username = "
+ username + " password = " + password);
return "Axios Ajax"; // 返回响应信息
}
}
ResponseEntity 类
ResponseEntity 类用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文。常用于文件上传和下载,相关内容如下。
文件上传和下载
文件下载
使用 ResponseEntity 类作为控制器方法的返回值类型来实现文件下载,代码比较固定。
@RequestMapping("/testDownload")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException
{
// 获取 ServletContext 对象
ServletContext servletContext = session.getServletContext();
// 获取服务器中文件的真实路径
String realPath
= servletContext.getRealPath("/static/img/img.png");
// 创建输入流
InputStream is = new FileInputStream(realPath);
// 创建字节数组
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.read(bytes);
// 创建 HttpHeaders 对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
// 设置要下载方式以及下载文件的名字
// 下载后的文件名可修改
headers.add("Content-Disposition", "attachment;filename=img_copy.png");
// 设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
// 创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity
= new ResponseEntity<>(bytes, headers, statusCode);
// 关闭输入流
is.close();
return responseEntity;
}
文件上传
文件上传要求 form 表单的请求方式必须为 post,并且添加属性 enctype=“multipart/form-data”。SpringMVC 中将上传的文件封装到 MultipartFile 对象中,并把对象引用赋给相应控制器方法的形参,通过此对象可以获取文件相关信息。
<!-- method 和 enctype 必须如此设置 -->
<form th:action="@{/testUpload}" method="post"
enctype="multipart/form-data">
头像:<input type="file" name="multipartFile"/><br/>
<input type="submit" value="上传"/>
</form>
上传配置步骤
添加 commons-fileupload 依赖。(上传需要,下载不需要)
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency>
在SpringMVC 的配置文件中配置文件上传解析器。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> </bean>
添加实现上传功能的控制器方法。
@RequestMapping("/testUpload") public ModelAndView testUpload (MultipartFile multipartFile, HttpSession session, ModelAndView mav) throws IOException { // 获取上传文件的文件名 String originalFilename = multipartFile.getOriginalFilename(); // 获取上传文件的后缀 String suffixName = originalFilename.substring (originalFilename.lastIndexOf(".")); // 通过 ServletContext 获取服务器中 picture 目录的路径 ServletContext servletContext = session.getServletContext(); String picturePath = servletContext.getRealPath("picture"); // 将 UUID 作为文件名 String uuid = UUID.randomUUID().toString().replaceAll("-", ""); // 将 UUID 和后缀名拼接的结果作为最终的文件名 String fileName = uuid + suffixName; // 判断 picturePath 所对应路径是否存在 File file = new File(picturePath); if (!file.exists()) { file.mkdir(); // 不存在则创建目录 } // picture 目录的路径拼接分隔符以及文件名得到最终路径 String finalPath = picturePath + File.separator + fileName; // 向浏览器响应,发送文件 multipartFile.transferTo(new File(finalPath)); mav.setViewName("success"); return mav; }
拦截器
在 SpringMVC 使用过程中,filter 作用于浏览器和前端控制器 Dispaerservlet 之间,前端控制器之后的的拦截器作用于控制器方法的执行的前后,用于在请求过程的不同阶段拦截请求。拦截器是一个类,需要实现 HandlerInterceptor 接口。
@Component
public class FirstInterceptor implements HandlerInterceptor
{
}
拦截器的配置
拦截器要在 SpringMVC 的配置文件中进行配置。
<!-- 配置拦截器-->
<mvc:interceptors>
<ref bean="firstInterceptor"/>
<ref bean="secondInterceptor"/>
<!-- <bean class="com.insight.mvc.interceptor.FirstInterceptor"/> -->
<!-- <ref bean="firstInterceptor"/>(需要使用注解 @Component 标识拦截器) -->
<!-- 以上两种配置方式都是对 DispatcherServlet 所处理的所有请求进行拦截 -->
<mvc:interceptor>
<mvc:mapping path="/**"/> <!-- 拦截所有请求(/* 仅拦截应用程序上下文后一层路径) -->
<mvc:exclude-mapping path="/"/> <!-- 排除主页面 -->
<ref bean="firstInterceptor"/>
</mvc:interceptor>
<!--
以上配置方式可以通过 ref 或 bean 标签设置拦截器
通过 mvc:mapping 设置需要拦截的请求
通过 mvc:exclude-mapping 设置不需要拦截的请求
-->
</mvc:interceptors>
拦截器的三个处理方法
SpringMVC 中的拦截器有三个处理方法,在控制器方法执行的前后执行。
方法名 | 作用 |
---|---|
preHandle | 控制器方法执行之前执行。返回 true 表示放行,即调用控制器方法;返回false 表示拦截,即不调用控制器方法。 |
postHandle | 控制器方法执行之后执行。 |
afterComplation | 处理完视图和模型的数据,且渲染视图后执行。(postHandle 方法之后) |
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
System.out.println("FirstInterceptor.preHandle");
return true; // ture 表示放行,false 表示拦截
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception
{
System.out.println("FirstInterceptor.postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception
{
System.out.println("FirstInterceptor.afterCompletion");
}
多个拦截器的执行顺序
-
每个拦截器的 preHandle 方法都返回 true
此时多个拦截器的执行顺序和拦截器在 SpringMVC 配置文件中的配置顺序有关。preHandle 方法会按照配置的顺序执行,而 postHandle 和 afterComplation 方法会按照配置的反序执行。
-
某个拦截器的 preHandle 方法 返回 false
返回 false 的拦截器和它之前拦截器的 preHandle 方法都会执行。postHandle 方法都不执行。返回 false 的拦截器之前的拦截器的 afterComplation 方法会执行。
异常处理器
SpringMVC 提供处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver。 其实现类有:DefaultHandlerExceptionResolver 和 SimpleMappingExceptionResolver。DefaultHandlerExceptionResolver 是默认的异常处理器。
基于配置的异常处理
SpringMVC 提供了自定义的异常处理器 SimpleMappingExceptionResolver,使用需要在 SpringMVC 配置文件中进行配置。
<!-- 配置异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--
properties 的键表示处理器方法执行过程中出现的异常
properties 的值表示出现指定异常时,设置一个新的视图名称,跳转到指定页面
-->
<!-- 视图名称会被 Thymeleaf 解析 -->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--
设置 exceptionAttribute 属性,
将出现的异常信息在请求域中共享
-->
<property name="exceptionAttribute" value="ex"/>
</bean>
基于注解的异常处理
使用 @ControllerAdvice 将类标识为异常处理的组件,再使用 @ExceptionHandler 类标识方法来处理异常。在方法形参中加入 Exception 类形参可以通过这一形参获取异常信息。
@ControllerAdvice // 标识为异常处理组件
public class ExceptionController
{
// 标识为异常处理方法,value 值为能处理的异常类的 class 对象数组
@ExceptionHandler
(value = {ArithmeticException.class,NullPointerException.class})
public ModelAndView testException(Exception ex) // Exception 类形参获取异常信息
{
ModelAndView mav = new ModelAndView();
System.out.println("ex = " + ex);
mav.addObject("ex", ex);
mav.setViewName("error");
return mav;
}
}
注解配置
使用配置类和注解代替 web.xml 文件和 SpringMVC 配置文件的功能。
创建初始化类,代替 web.xml
当类实现了 AbstractAnnotationConfigDispatcherServletInitializer 接口并将其部署到 Servlet 容器的时候,容器会自动发现它,并用它来配置 Servlet 上下文。
// web 项目的初始化类,用来代替 web.xml
public class WebInit
extends AbstractAnnotationConfigDispatcherServletInitializer
{
// 指定 Spring 的配置类
@Override
protected Class<?>[] getRootConfigClasses()
{
return new Class[]{SpringConfig.class};
}
// 指定 SpringMVC 的配置类
@Override
protected Class<?>[] getServletConfigClasses()
{
return new Class[]{WebConfig.class};
}
// 指定 DispatcherServlet 的映射规则,即 url-pattern
@Override
protected String[] getServletMappings()
{
return new String[]{"/"};
}
// 添加过滤器
@Override
protected Filter[] getServletFilters()
{
CharacterEncodingFilter encodingFilter
= new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
encodingFilter.setForceResponseEncoding(true);
HiddenHttpMethodFilter methodFilter // 用于发送 put 和 delete 请求
= new HiddenHttpMethodFilter();
return new Filter[]{encodingFilter, methodFilter};
}
}
创建 SpringConfig 配置类,代替 spring 的配置文件
@Configuration
public class SpringConfig
{
// ssm 整合之后,Spring 的配置信息写在此类中
}
创建 WebConfig 配置类,代替 SpringMVC 的配置文件
/**
* 代替 SpringMVC 配置文件
*/
@Configuration
//扫描组件
@ComponentScan("com.insight.mvc.controller")
//开启 MVC 注解驱动
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer
{
// 处理静态资源
@Override
public void configureDefaultServletHandling
(DefaultServletHandlerConfigurer configurer)
{
configurer.enable();
}
// 配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry)
{
TestInterceptor testInterceptor = new TestInterceptor();
registry.addInterceptor(testInterceptor).addPathPatterns("/**");
}
// 配置视图控制器
@Override
public void addViewControllers(ViewControllerRegistry registry)
{
registry.addViewController("/").setViewName("index");
}
// 文件上传解析器
@Bean
public MultipartResolver multipartResolver()
{
return new CommonsMultipartResolver();
}
// 异常处理
@Override
public void configureHandlerExceptionResolvers
(List<HandlerExceptionResolver> resolvers)
{
SimpleMappingExceptionResolver exceptionResolver
= new SimpleMappingExceptionResolver();
Properties properties = new Properties();
//设置异常映射
properties.setProperty("java.lang.ArithmeticException", "error");
exceptionResolver.setExceptionMappings(properties);
//设置共享异常信息的键
exceptionResolver.setExceptionAttribute("exception");
resolvers.add(exceptionResolver);
}
// 配置生成模板解析器
@Bean
public ITemplateResolver templateResolver()
{
WebApplicationContext webApplicationContext =
ContextLoader.getCurrentWebApplicationContext();
// ServletContextTemplateResolver 需要 ServletContext 作为构造参数
// 可通过 WebApplicationContext 的方法获得
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;
}
}
常用组件和执行流程
常用组件
- DispatcherServlet:前端控制器,由框架提供。作用:统一处理请求和响应,作为整个流程控制的中心,调用其它组件处理用户的请求。
- HandlerMapping:控制器的映射器,由框架提供。作用:根据请求的 url、method 等信息查找控制器方法。
- Handler:控制器,由工程师编写。作用:在 DispatcherServlet 的控制下的控制器对具体的用户请求进行处理。
- HandlerAdapter:处理器适配器,由框架提供。作用:执行控制器方法。
- ViewResolver:视图解析器,由框架提供。作用:进行视图解析,得到相应的视图。
- View:视图。作用:将模型数据通过页面展示给用户。
执行流程
用户向服务器发送请求,请求被 SpringMVC 的前端控制器 DispatcherServlet 捕获。
DispatcherServlet 对请求 URL 进行解析,得到请求资源标识符(URI),判断 URI 对应的映射是否存在。
不存在。判断是否配置了 mvc:default-servlet-handler。
如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML)。如果没配置,则会报错,请求失败。
如果存在,则执行以下流程:
根据该 URI,调用 HandlerMapping 获得该 Handler 配置的所有相关的对象(包括Handler 对象以及 Handler 对象对应的拦截器),最后以 HandlerExecutionChain 执行链对象的形式返回。
DispatcherServlet 根据获得的 Handler,选择一个合适的 HandlerAdapter。
如果成功获得 HandlerAdapter,此时将开始执行拦截器的 preHandler 方法。(正序)
提取 Request 中的模型数据,赋值 Handler 形参,开始执行 Handler(Controller) 方法,处理请求。 在赋值 Handler 的形参过程中,根据配置,Spring 将做一些额外的工作:
a)HttpMessageConveter:将请求消息(如 Json、xml 等数据)转换成一个对象,将对象转换为指定的响应信息。
b)数据转换:对请求消息进行数据转换。如 String 转换成 Integer、Double 等。
c)数据格式化:对请求消息进行数据格式化。如将字符串转换成格式化数字或格式化日期等。 d)数据验证:验证数据的有效性(长度、格式等),验证结果存储到 BindingResult 或Error 中。
Handler 执行完成后,向 DispatcherServlet 返回一个 ModelAndView 对象。
此时将开始执行拦截器的 postHandle 方法。(逆序)
根据返回的 ModelAndView(此时会判断是否存在异常,HandlerExceptionResolver 对存在的异常进行处理)选择一个适合的 ViewResolver 进行视图解析,根据 Model 和 View 来渲染视图。
渲染视图完毕后,执行拦截器的 afterCompletion 方法。(逆序)
将渲染结果返回给客户端。