2022尚硅谷SSM框架跟学 八 Spring MVC基础三
- 8.RESTful案例
- 8.1准备工作
- 8.2功能清单
- 8.3.具体功能:访问首页
- (1).配置view-controller
- (2).创建页面
- 8.4具体功能:查询所有员工数据
- (1).控制器方法
- (2).创建employee_list.html
- 8.5具体功能:删除
- (1).创建处理delete请求方式的表单
- (2).删除超链接绑定点击事件
- (3).控制器方法
- 8.6具体功能:跳转到添加数据页面
- (1).配置view-controller
- (2).创建employee_add.html
- 8.7具体功能:执行保存(添加)
- (1).控制器方法
- 8.8具体功能:跳转到更新数据页面
- (1).修改超链接
- (2).控制器方法
- (3).创建employee_update.html
- 8.9具体功能:执行更新
- (1).控制器方法
- 9.SpringMVC处理ajax请求
- 9.1@RequestBody
- 9.2@RequestBody获取json格式的请求参数
- 9.3@ResponseBody
- 9.4@ResponseBody响应浏览器json数据
- 9.5@RestController注解
- 10.文件上传和下载
- 10.1文件下载
- 10.2文件上传
- (1).添加依赖
- (2).在SpringMVC的配置文件中添加配置
- (3).控制器方法
- 11.拦截器
- 11.1拦截器的配置
- 11.2拦截器的三个抽象方法
- (1).若拦截器的preHandle()返回true
- (2).若拦截器的preHandle()返回了false
- (3).拦截器的配置
- 方式一:在 < mvc:interceptors >外配置bean,然后用ref引用bean id即可。
- 方式二:通过注解+扫描的方式
- 方式三:mvc配置
- 11.3多个拦截器的执行顺序
- (1).若每个拦截器的preHandle()都返回true
- (2).若某个拦截器的preHandle()返回了false
- (3).若所有拦截器的preHandle()都返回了false
- 12.异常处理器
- 12.1基于配置的异常处理
- 12.2基于注解的异常处理
- 13.注解配置SpringMVC
- 13.1创建初始化类,代替web.xml
- 13.2创建SpringConfig配置类,代替spring的配置文件
- 13.3创建WebConfig配置类,代替SpringMVC的配置文件
- 13.4测试功能
8.RESTful案例
8.1准备工作
和传统 CRUD 一样,实现对员工信息的增删改查。
- 搭建环境
- 准备实体类
创建实体类Employee.java
代码如下:
package com.atguigu.pojo;
/**
* @ClassName: Employee
* @Description:
* @Author: wty
* @Date: 2023/1/31
*/
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() {
}
}
- 准备dao模拟数据
代码如下:
package com.atguigu.dao;
import com.atguigu.pojo.Employee;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName: EmployeeDao
* @Description:
* @Author: wty
* @Date: 2023/1/31
*/
@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) {
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);
}
}
创建控制层EmployeeController.java
8.2功能清单
功能 | URL 地址 | 请求方式 |
---|---|---|
访问首页√ | / | GET |
查询全部数据√ | /employee | GET |
删除√ | /employee/2 | DELETE |
跳转到添加数据页面√ | /toAdd | GET |
执行保存√ | /employee | POST |
跳转到更新数据页面√ | /employee/2 | GET |
执行更新√ | /employee | PUT |
8.3.具体功能:访问首页
(1).配置view-controller
在springmvc.xml增加如下配置
<!-- 配置视图控制器 -->
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
(2).创建页面
给控制层EmployeeController.java添加代码,要使用Dao层调用业务逻辑。
@Autowired
private EmployeeDao employeeDao;
只是这么加的话,发现自动装配的注解报错了
于是我们更改springmvc.xml,扩大扫描控制层的包的范围
<!-- 扫描控制层组件(自己配置package) -->
<context:component-scan base-package="com.atguigu"></context:component-scan>
在index.html中添加代码
<hr>
<a th:href="@{/employee}">查询所有的员工信息</a><br>
8.4具体功能:查询所有员工数据
(1).控制器方法
在EmployeeController.java中添加方法
/**
* 查询所有的员工信息-->/employee-->GET
*/
@RequestMapping(value = "/employee", method = RequestMethod.GET)
public String getAllEmployee(ModelMap map) {
// 获取所有员工信息
Collection<Employee> all = employeeDao.getAll();
// 将所有的员工信息在请求域中共享
map.addAttribute("all", all);
return "employee_list";
}
(2).创建employee_list.html
代码如下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>employee_list</title>
</head>
<body>
<table>
<tr>
<th colspan="5">employee_list</th>
</tr>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>操作</th>
</tr>
<tr th:each="employee:${all}">
<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 href="">删除</a>
<a href="">修改</a>
</td>
</tr>
</table>
</body>
</html>
发现有红色波浪线
在settings中搜索thymeleaf,关掉检查
之后红色波浪线消失了
一般情况,建议还是把勾打上,有红线就有红线将就看,如果关掉检查,真的有错误了,自己也不太好排查。
部署Tomcat,看一下页面展示效果
点击超链接后跳转到列表展示
看到展示的列表很难看,现在我们加上样式。
把课件中的webapp拷贝到webapp目录之下
因为刚导入的渲染,并不会立即生效,所以我们把Maven清空一下
清理完成后点击重新打包
看到target目录下有static即为成功部署
修改employee_list.html
<link rel="stylesheet" th:href="@{/static/css/index_work.css}">
重新部署Tomcat,发现页面还是没有加载样式,这里按F12
原因就在于静态资源的处理,不是经过DispatcherServlet,这种情况我们参考一下Tomcat的配置文件
打开Tomcat的web.xml后查看L103左右,有静态资源的加载设置。
下面L388行,DefaultServlet配置的url-pattern是/
工程中的web.xml是逻辑上继承自Tomcat的web.xml,比如Tomcat中的欢迎列表
假如我们在工程的web.xml中更换了欢迎页的地址,那么优先以工程中更换后的欢迎页为跳转。
这里明白后,静态界面加载不出来的原因就如下:
所以要修改springmvc.xml,增加代码
<!--
配置默认的servlet处理静态资源
当前工程的web.xml配置的前端控制器DispatcherServlet的url-pattern是/
Tomcat的web.xml配置的DefaultServlet的url-pattern也是/
此时浏览器发送的请求,优先按照工程的DispatcherServlet处理,所以静态资源处理不了
若配置了<mvc:default-servlet-handler/>,浏览器发送的请求都会被DefaultServlet处理
如果配置了<mvc:default-servlet-handler/>和<mvc:annotation-driven/>浏览器发送的请求会先被DispatcherServlet处理,无法处理后再交给DefaultServlet处理
-->
<mvc:default-servlet-handler/>
这里要注意,如果要配置默认的servlet,则必不可少开启mvc的注解驱动,这里是因为配置了视图控制器,提前加上了开启mvc的注解驱动,这里要注意,如果没配置视图控制器的情况下,也不能少mvc的注解驱动。
此时再重新部署Tomcat看一下,样式加载成功
结论
配置默认的servlet处理静态资源
当前工程的web.xml配置的前端控制器DispatcherServlet的url-pattern是/,Tomcat的web.xml配置的DefaultServlet的url-pattern也是/,此时浏览器发送的请求,优先按照工程的DispatcherServlet处理,所以静态资源处理不了。
< a >若配置了< mvc:default-servlet-handler/ >,浏览器发送的请求都会被DefaultServlet处理。
< b >如果配置了< mvc:default-servlet-handler/>和<mvc:annotation-driven/ >浏览器发送的请求会先被DispatcherServlet处理,无法处理后再交给DefaultServlet处理。
8.5具体功能:删除
(1).创建处理delete请求方式的表单
修改employee_list.html
<!-- 作用:通过超链接控制表单的提交,将post请求转换为delete请求 -->
<form method="post" id="delete_form">
<!-- HiddenHttpMethodFilter要求:必须传输_method请求参数,并且值为最终的请求方式 -->
<input type="hidden" name="_method" value="delete">
</form>
添加div标签
(2).删除超链接绑定点击事件
引入vue.js
代码如下
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
删除超链接
修改employee_list.html,增加单击事件
代码如下:
<a @click="deleteEmployee" th:href="@{'/employee/'+${employee.id}}">删除</a>
通过vue处理点击事件
代码如下:
<script type="text/javascript">
var vue = new Vue({
el: "#app",
methods: {
deleteEmployee() {
// 获取form表单
var form = document.getElementsByTagName("form")[0];
//var form = document.getElementById("delete_form");
// 将超链接的href属性值赋值给form表单的action属性
// event.target表示当前触发事件的标签
form.action = event.target.href;
// 表单提交
form.submit();
// 阻止超链接的默认行为
event.preventDefault();
}
}
});
</script>
(3).控制器方法
修改EmployeeController.java
/**
* 删除员工信息-->/employee/1-->DELETE
*/
@RequestMapping(value = "/employee/{id}", method = RequestMethod.DELETE)
public String deleteEmployee(@PathVariable("id") Integer id) {
// 删除员工的信息
employeeDao.delete(id);
return "redirect:/employee";
}
重新部署一下Tomcat,点击删除最后一条数据
发现删除成功
8.6具体功能:跳转到添加数据页面
(1).配置view-controller
修改employee_list.html
代码如下:
<th>操作(<a th:href="@{/to/add}">+</a> )</th>
这里的跳转地址也可以配置成视图加载
修改springmvc.xml增加代码
代码如下:
<mvc:view-controller path="/to/add" view-name="employee_list_add"></mvc:view-controller>
重新部署后,查看效果
(2).创建employee_add.html
那么跳转添加界面需要新建一个employee_list_add.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>add employee</title>
<link rel="stylesheet" th:href="@{/static/css/index_work.css}">
</head>
<body>
<form th:action="@{/employee}" method="post">
<table>
<tr>
<th colspan="2">add employee</th>
</tr>
<tr>
<td>lastName</td>
<td>
<input type="text" name="lastName">
</td>
</tr>
<tr>
<td>email</td>
<td>
<input type="text" name="email">
</td>
</tr>
<tr>
<td>gender</td>
<td>
<input type="radio" name="gender" value="1">male
<input type="radio" name="gender" value="0">female
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="提交">
</td>
</tr>
</table>
</form>
</body>
</html>
8.7具体功能:执行保存(添加)
(1).控制器方法
修改EmployeeController.java
/**
* 跳转到添加页面-->/to/add-->GET
* 添加-->/employee-->POST
* 注意这里是从employee_list_add.html跳转过来的
*/
@RequestMapping(value = "/employee", method = RequestMethod.POST)
public String insertEmployee(Employee employee) {
// 保存员工信息
employeeDao.save(employee);
// 重新跳转到getAllEmployee的请求
return "redirect:/employee";
}
重新部署Tomcat
单击+号后,跳转界面
重新部署即可
提交后,发现添加成功
8.8具体功能:跳转到更新数据页面
(1).修改超链接
修改employee_list.html
这里要注意,有跳转的逻辑地址和参数时,用‘’将地址括起来
代码如下
<a th:href="@{'/employee/'+${employee.id}}">修改</a>
重新部署,鼠标悬停添加上,看跳转地址正确
(2).控制器方法
修改EmployeeController.java,增加方法
注意不要忘记加注释@PathVariable
/**
* 跳转到修改页面-->/employee/1-->GET
*/
@RequestMapping(value = "/employee/{id}", method = RequestMethod.GET)
public String toUpdateEmployee(@PathVariable("id")Integer id, Map<String, Object> map) {
// 跳转到员工修改信息,根据id查询员工信息
Employee employee = employeeDao.get(id);
// 把employee共享到共享域中
map.put("employee", employee);
return "employee_update";
}
(3).创建employee_update.html
增加页面
代码如下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>employee_update</title>
<link rel="stylesheet" th:href="@{/static/css/index_work.css}">
</head>
<body>
<form th:action="@{/employee}" method="post">
<input type="hidden" name="_method" value="put">
<input type="hidden" name="id" th:value="${employee.id}">
<table>
<tr>
<th colspan="2">update employee</th>
</tr>
<tr>
<td>lastName</td>
<td>
<input type="text" name="lastName" th:value="${employee.lastName}">
</td>
</tr>
<tr>
<td>email</td>
<td>
<input type="text" name="email" th:value="${employee.email}">
</td>
</tr>
<tr>
<td>gender</td>
<td>
<input type="radio" name="gender" value="1" th:field="${employee.gender}">male
<input type="radio" name="gender" value="0" th:field="${employee.gender}">female
</td>
</tr>
<tr>
<td colspan="2">
<input type="submit" value="更新">
</td>
</tr>
</table>
</form>
</body>
</html>
重新部署Tomcat,跳转成功
当前界面单击鼠标右键,选择查看源代码
查看源代码如下
8.9具体功能:执行更新
(1).控制器方法
修改EmployeeController.java
/**
* 修改员工信息-->/employee-->PUT
*/
@RequestMapping(value = "/employee", method = RequestMethod.PUT)
public String updateEmployee(Employee employee) {
// 保存员工修改的信息
employeeDao.save(employee);
return "redirect:/employee";
}
重新部署Tomcat
修改之后点击更新,更新内容如下:
9.SpringMVC处理ajax请求
新创建一个项目
Name:spring-mvc-ajax
GroupId:com.atguigu
拷贝上一个项目的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SSM</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>war</packaging>
<groupId>com.atguigu</groupId>
<artifactId>spring-mvc-ajax</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- ServletAPI -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Spring5和Thymeleaf整合包 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
</project>
添加web.xml
地址如下:
F:\javawebwork\SSM\spring-mvc-ajax\src\main\webapp\WEB-INF\web.xml
web.xml中加入相关配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 编码过滤器 -->
<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>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 设置处理请求方法的过滤器 -->
<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>
<!-- 前端控制器 -->
<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>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
创建首页index.html
首页代码如下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>index.html</h1>
</body>
</html>
拷贝之前项目的springmvc.xml即可
<?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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 扫描控制层组件(自己配置package) -->
<context:component-scan base-package="com.atguigu.controller"></context:component-scan>
<!-- 配置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">
<!--
比如当前index的实际位置在/WEB-INF/templates/index.html(物理视图)
去掉视图前缀变为 index.html
去掉视图后缀变为 index(逻辑视图)
-->
<!-- 视图前缀 -->
<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:view-controller path="/" view-name="index"></mvc:view-controller>
<!-- 开启mvc注解驱动 -->
<mvc:annotation-driven/>
<!--
配置默认的servlet处理静态资源
当前工程的web.xml配置的前端控制器DispatcherServlet的url-pattern是/
Tomcat的web.xml配置的DefaultServlet的url-pattern也是/
此时浏览器发送的请求,优先按照工程的DispatcherServlet处理,所以静态资源处理不了
若配置了<mvc:default-servlet-handler/>,浏览器发送的请求都会被DefaultServlet处理
如果配置了<mvc:default-servlet-handler/>和<mvc:annotation-driven/>浏览器发送的请求会先被DispatcherServlet处理,无法处理后再交给DefaultServlet处理
-->
<mvc:default-servlet-handler/>
</beans>
创建控制层TestAjaxController
部署Tomcat,配置启动方式
配置地址
启动部署Tomcat,正常部署
在官网中查看axious的用法
axious文档官网
继续修改index.html
代码如下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<div id="app">
<h1>index.html</h1>
<input type="button" value="测试SpringMVC处理ajax请求" @click="testAjax">
</div>
<script type="text/javascript" th:src="@{/js/vue.js}"></script>
<script type="text/javascript" th:src="@{/js/axios.min.js}"></script>
<script type="text/javascript">
/**
* axios({
* url: "",//请求路径
* method: "",//请求方式
* params: {},// 以name = value&name = value的方式发送请求参数,不管请求方式是get还是post,
* 请求参数都会被拼接到请求地址后;此种方式的请求参数可以通过request.getParameter获取参数。
* data: {}// 以json格式发送请求参数,请求参数会被保存到请求报文的请求体保存到服务器,用GSON获取参数
* }).then(response => {
* console.log(response.data);
* });
*/
var vue = new Vue({
el: "#app",
methods: {
testAjax() {
axios.post(
"/SpringMVC/test/ajax?id=1001",
{username: "admin", password: "123456"}
).then(response => {
console.log(response.data);
});
}
}
});
</script>
</body>
</html>
修改控制层TestAjaxController.java
@Controller
public class TestAjaxController {
@RequestMapping("test/ajax")
public void testAjax(Integer id, HttpServletResponse response) throws IOException {
System.out.println("id:" + id);
response.getWriter().write("hello,axios");
}
}
创建success.html
代码如下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>成功</title>
</head>
<body>
<h1>success.html</h1>
</body>
</html>
重新部署Tomcat,打开F12切换到控制台,点击按钮,浏览器控制台输出语句。
后台控制台,获取到id
F12切换到网络
9.1@RequestBody
@RequestBody可以获取请求体信息,使用@RequestBody注解标识控制器方法的形参,当前请求的请求体就会为当前注解所标识的形参赋值。
修改TestAjaxController.java
@Controller
public class TestAjaxController {
@RequestMapping("test/ajax")
public void testAjax(Integer id, @RequestBody String requestBody, HttpServletResponse response) throws IOException {
System.out.println("requestBody:" + requestBody);
System.out.println("id:" + id);
response.getWriter().write("hello,axios");
}
}
重新部署一下Tomcat,看一下输出结果requestBody
输出结果:
requestBody:username=admin&password=123456
总结
@RequestBody将请求体中的内容和控制器方法的形参进行绑定
9.2@RequestBody获取json格式的请求参数
在使用了axios发送ajax请求之后,浏览器发送到服务器的请求参数有两种格式:
1、name=value&name=value…,此时的请求参数可以通过request.getParameter()获取,对应SpringMVC中,可以直接通过控制器方法的形参获取此类请求参数。
2、{key:value,key:value,…},此时无法通过request.getParameter()获取,之前我们使用操作json的相关jar包gson或jackson处理此类请求参数,可以将其转换为指定的实体类对象或map集合。在SpringMVC中,直接使用@RequestBody注解标识控制器方法的形参即可将此类请求参数转换为java对象。
使用@RequestBody获取json格式的请求参数的条件:
1.导入jackson的依赖
代码如下:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
2.SpringMVC的配置文件中设置开启mvc的注解驱动
代码如下:
<!--开启mvc的注解驱动-->
<mvc:annotation-driven />
3.在控制器方法的形参位置,设置json格式的请求参数要转换成的java类型(实体类或map)的参数,并使用@RequestBody注解标识
修改index.html,增加超链接标签。
创建实体类User
代码如下:
package com.atguigu.pojo;
/**
* @ClassName: User
* @Description:
* @Author: wty
* @Date: 2023/1/31
*/
public class User {
private Integer id;
private String username;
private String password;
private String gender;
private Integer age;
public User() {
}
public User(Integer id, String username, String password, String gender, Integer age) {
this.id = id;
this.username = username;
this.password = password;
this.gender = gender;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", gender='" + gender + '\'' +
", age=" + age +
'}';
}
}
<input type="button" value="使用@RequestBody注解处理json格式的请求参数" @click="testRequestBody">
增加axios
testRequestBody() {
axios.post(
"/SpringMVC/test/RequestBody/json",
{username: "admin", password: "123456", gender: "男", age: 23}
).then(response => {
console.log(response.data);
})
}
修改TestAjaxController.java,增加方法
@RequestMapping("/test/RequestBody/json")
public void testRequestBody(@RequestBody User user, HttpServletResponse response) throws IOException {
response.getWriter().write("hello,RequestBody");
System.out.println(user);
}
重新部署Tomcat,单击新加的超链接
看一下后台的控制台
我们继续更改TestAjaxController.java用另一种方式来接收
@RequestMapping("/test/RequestBody/json")
public void testRequestBody(@RequestBody Map<String, Object> map, HttpServletResponse response) throws IOException {
response.getWriter().write("hello,RequestBody");
System.out.println(map);
}
用map来接收
再重新部署一下Tomcat,看一下后台的输出
9.3@ResponseBody
@ResponseBody用于标识一个控制器方法,可以将该方法的返回值直接作为响应报文的响应体响应到浏览器。
在index.html添加超链接
<a th:href="@{/test/ResponseBody}">测试@ResponseBody注解响应浏览器数据</a>
修改TestAjaxController.java
@RequestMapping("/test/ResponseBody")
public String testResponseBody() {
return "success";
}
重新部署Tomcat
点击 超链接后,成功跳转
紧接着我们给TestAjaxController.java的方法加上@ResponseBody注解
@RequestMapping("/test/ResponseBody")
@ResponseBody
public String testResponseBody() {
return "success";
}
重新部署后,再点击超链接
我们发现,@ResponseBody注解把方法testResponseBody的返回值作为了响应体返回到了页面。
总结
@ResponseBody注解:将所标识的控制器方法的返回值作为响应报文的响应体响应到浏览器。
9.4@ResponseBody响应浏览器json数据
服务器处理ajax请求之后,大多数情况都需要向浏览器响应一个java对象,此时必须将java对象转换为json字符串才可以响应到浏览器,之前我们使用操作json数据的jar包gson或jackson将java对象转换为json字符串。在SpringMVC中,我们可以直接使用@ResponseBody注解实现此功能@ResponseBody响应浏览器json数据的条件:
1.导入jackson的依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
2.SpringMVC的配置文件中设置开启mvc的注解驱动
<!--开启mvc的注解驱动-->
<mvc:annotation-driven />
3.使用@ResponseBody注解标识控制器方法,在方法中,将需要转换为json字符串并响应到浏览器的java对象作为控制器方法的返回值,此时SpringMVC就可以将此对象直接转换为json字符串并响应到浏览器
修改index.html,添加标签
<input type="button" value="使用@ResponseBody注解响应浏览器json格式的数据" @click="testResponseBody">
增加axios
testResponseBody() {
axios.post(
"/SpringMVC/test/ResponseBody/json"
).then(response => {
console.log(response.data);
})
}
修改TestAjaxController.java
@RequestMapping("test/ResponseBody/json")
@ResponseBody
public User testResponseBodyJson() {
User user = new User(1001, "admin", "123456", "男", 21);
return user;
}
重新部署Tomcat
按F12控制台输出json对象
那换一种返回值呢,我们来看下Map集合
修改TestAjaxController.java
@RequestMapping("test/ResponseBody/json")
@ResponseBody
public Map<String, Object> testResponseBodyJson() {
User user1 = new User(1001, "admin1", "123456", "男", 21);
User user2 = new User(1002, "admin2", "123", "女", 22);
User user3 = new User(1003, "admin3", "456", "男", 23);
Map<String, Object> map = new HashMap<>();
map.put("1001", user1);
map.put("1002", user2);
map.put("1003", user3);
return map;
}
重新部署Tomcat
那再换一种返回值,我们来看下List集合
修改TestAjaxController.java
@RequestMapping("test/ResponseBody/json")
@ResponseBody
public List<Object> testResponseBodyJson() {
User user1 = new User(1001, "admin1", "123456", "男", 21);
User user2 = new User(1002, "admin2", "123", "女", 22);
User user3 = new User(1003, "admin3", "456", "男", 23);
List<Object> list = new ArrayList<>();
list.add(user1);
list.add(user2);
list.add(user3);
return list;
}
总结
常用的Java对象(后台)转换为json(前台)的结果:
实体类→json对象
map集合→json对象
list集合→json数组
9.5@RestController注解
@RestController注解是springMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了@Controller注解,并且为其中的每个方法添加了@ResponseBody注解。
10.文件上传和下载
10.1文件下载
ResponseEntity用于控制器方法的返回值类型,该控制器方法的返回值就是响应到浏览器的响应报文使用ResponseEntity实现下载文件的功能。
首先准备一张图片,放到webapp下,新建目录img
图片拷贝完,要整体清空Maven
确保target中有图片
修改index.html,增加一个超链接
<a th:href="@{/test/down}">下载图片</a>
创建一个控制层FileUpAndDownController.java
@Controller
public class FileUpAndDownController {
/**
* @param
* @return org.springframework.http.ResponseEntity<byte [ ]>
* @description //TODO
* @param: session
* @date 2023/2/1 0:30
* @author wty
**/
@RequestMapping("/test/down")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
//String realPath = servletContext.getRealPath("/img/1.PNG");
String realPath = servletContext.getRealPath("img");
// 可以用文件分隔符来表示\或者/
realPath = realPath + File.separator + "1.PNG";
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组 is.available()获取输入流所对应文件的字节数
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字,以附件形式下载文件
headers.add("Content-Disposition", "attachment;filename=1.PNG");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
//bytes, 响应体
// headers, 响应头
// statusCode 响应状态码
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}
}
重新部署Tomcat
下载成功
10.2文件上传
文件上传要求form表单的请求方式必须为post,并且添加属性enctype=“multipart/form-data”
SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息。
修改index.html
<!--multipart/form-data:当前表单数据以二进制形式提交到服务中 -->
<form th:action="@{/test/up}" method="post" enctype="multipart/form-data">
头像:<input type="file" name="photo"><br>
<input type="submit" value="上传">
</form>
上传步骤:
(1).添加依赖
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload --
>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
(2).在SpringMVC的配置文件中添加配置
在springmvc.xml中添加配置
<!-- 配置文件上传解析器 -->
<!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置编码 -->
<!--<property name="defaultEncoding" value="UTF-8"></property>-->
<!-- 设置最大上传值 -->
<!--<property name="maxUploadSize" value="102400"></property>-->
</bean>
(3).控制器方法
FileUpAndDownController.java中新增上传方法
/**
* @param
* @return java.lang.String
* @description //文件上传
* @param: photo
* @param: session
* @date 2023/2/1 12:07
* @author wty
**/
@RequestMapping("/test/up")
public String testUp(MultipartFile photo, HttpSession session) throws IOException {
//获取上传的文件的文件名
String fileName = photo.getOriginalFilename();
//处理文件重名问题
String hzName = fileName.substring(fileName.lastIndexOf("."));
fileName = UUID.randomUUID().toString() + hzName;
//获取服务器中photo目录的路径
ServletContext servletContext = session.getServletContext();
// 获取工程下Tomcat中photo目录下的真实路径
String photoPath = servletContext.getRealPath("photo");
File file = new File(photoPath);
// 判断photo目录是否存在
if (!file.exists()) {
// 不存在就创建photo目录
file.mkdir();
}
// 最终上传的路径
String finalPath = photoPath + File.separator + fileName;
//实现上传功能
photo.transferTo(new File(finalPath));
return "success";
}
重新部署Tomcat
选择1张图片
点击上传,成功跳转
看一下Tomcat的目录,成功生成图片,因为名称会重复,这里代码设置的图片名称为UUID。
UUID如下段代码设置
11.拦截器
过滤器是浏览器和目标资源之间拦截。
拦截器和过滤器图示如下:
新创建工程
Name:spring-mvc-extension
GroupId:com.atguigu
拷贝上一个工程的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SSM</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>war</packaging>
<groupId>com.atguigu</groupId>
<artifactId>spring-mvc-extension</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- ServletAPI -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Spring5和Thymeleaf整合包 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
</project>
创建web.xml
修改地址为
F:\javawebwork\SSM\spring-mvc-extension\src\main\webapp\WEB-INF\web.xml
在新创建的web.xml中进行配置
<!-- 编码过滤器 -->
<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>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 设置处理请求方法的过滤器 -->
<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>
<!-- 前端控制器 -->
<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>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
新建springmvc.xml,拷贝之前的项目
在springmvc.xml中配置
<!-- 扫描控制层组件(自己配置package) -->
<context:component-scan base-package="com.atguigu.controller"></context:component-scan>
<!-- 配置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">
<!--
比如当前index的实际位置在/WEB-INF/templates/index.html(物理视图)
去掉视图前缀变为 index.html
去掉视图后缀变为 index(逻辑视图)
-->
<!-- 视图前缀 -->
<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:view-controller path="/" view-name="index"></mvc:view-controller>
<!-- 开启mvc注解驱动 -->
<mvc:annotation-driven/>
<!--
配置默认的servlet处理静态资源
当前工程的web.xml配置的前端控制器DispatcherServlet的url-pattern是/
Tomcat的web.xml配置的DefaultServlet的url-pattern也是/
此时浏览器发送的请求,优先按照工程的DispatcherServlet处理,所以静态资源处理不了
若配置了<mvc:default-servlet-handler/>,浏览器发送的请求都会被DefaultServlet处理
如果配置了<mvc:default-servlet-handler/>和<mvc:annotation-driven/>浏览器发送的请求会先被DispatcherServlet处理,无法处理后再交给DefaultServlet处理
-->
<mvc:default-servlet-handler/>
<!-- 配置文件上传解析器 -->
<!--必须通过文件解析器的解析才能将文件转换为MultipartFile对象-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置编码 -->
<!--<property name="defaultEncoding" value="UTF-8"></property>-->
<!-- 设置最大上传值 -->
<!--<property name="maxUploadSize" value="102400"></property>-->
</bean>
创建首页index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<h1>index.html</h1>
</body>
</html>
创建控制层TestController.java
@Controller
public class TestController {
@RequestMapping("/test/hello")
public String testHello() {
return "success";
}
}
创建跳转成功的界面success.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>成功</title>
</head>
<body>
<h1>success.html</h1>
</body>
</html>
配置Tomcat,启动
配置地址
启动Tomcat,页面加载成功
11.1拦截器的配置
SpringMVC中的拦截器用于拦截控制器方法的执行
SpringMVC中的拦截器需要实现HandlerInterceptor
SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置:
在index.html中添加语句
<a th:href="@{/test/hello}">测试拦截器</a>
修改TestController.java
修改FirstInterceptor.java
public class FirstInterceptor implements HandlerInterceptor {
}
都是default修饰的,所以不会报错,现在用ctrl+o重写一下这些方法
重写FirstInterceptor.java后如下
/**
* @ClassName: FirstInterceptor
* @Description:
* @Author: wty
* @Date: 2023/2/1
*/
public class FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor.preHandle");
return 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");
}
}
创建完拦截器后,我们重新部署Tomcat,这时候发现后台控制台,什么也没打印,说明拦截器此时并没有调用。
点击测试拦截器
需要在springmvc.xml中配置拦截器
<!-- 配置拦截器 -->
<mvc:interceptors>
<bean class="com.atguigu.interceptor.FirstInterceptor"></bean>
</mvc:interceptors>
重新部署Tomcat
看一下控制台的输出情况,只输出了preHandle
11.2拦截器的三个抽象方法
SpringMVC中的拦截器有三个抽象方法:
- preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法
- postHandle:控制器方法执行之后执行postHandle()
- afterCompletion:控制器方法执行之后且处理完视图和模型数据,渲染视图完毕之后执行afterCompletion()
(1).若拦截器的preHandle()返回true
将FirstInterceptor.java中preHandle方法设置为true
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor.preHandle");
return true;
}
重新部署Tomcat,点击拦截器
跳转成功
最后看一下后台输出,三个拦截器都输出了
(2).若拦截器的preHandle()返回了false
这里看一下源码DispatcherServlet.class
这里如果返回了false后,就直接跳出,之后的拦截器就不会执行了
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
(3).拦截器的配置
方式一:在 < mvc:interceptors >外配置bean,然后用ref引用bean id即可。
修改springmvc.xml
<bean id="firstInterceptor" class="com.atguigu.interceptor.FirstInterceptor"></bean>
<!-- 配置拦截器 -->
<mvc:interceptors>
<ref bean="firstInterceptor"></ref>
</mvc:interceptors>
方式二:通过注解+扫描的方式
这种方式配置的拦截器默认对DispatcherServlet处理的所有请求进行拦截
1.修改FirstInterceptor.java
加上注解
@Component
2.修改springmvc.xml
扫描控制层设置为
<context:component-scan base-package="com.atguigu"></context:component-scan>
3.修改springmvc.xml
<!-- 配置拦截器 -->
<mvc:interceptors>
<!--<bean class="com.atguigu.interceptor.FirstInterceptor"></bean>-->
<ref bean="firstInterceptor"></ref>
</mvc:interceptors>
重新部署Tomcat
跳转成功
后台输出拦截器的信息
我们在地址后随便加个后缀
发现页面是404,但是后台拦截器依然执行了
可见这种拦截器的配置方式是应用于所有页面的。
方式三:mvc配置
这种方式配置的拦截器比较精确
修改springmvc.xml
<!-- 配置拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 配置需要拦截额请求的请求路径 -->
<mvc:mapping path="/*"/>
<!-- 排除拦截 -->
<mvc:exclude-mapping path="/abc"/>
<ref bean="firstInterceptor"></ref>
</mvc:interceptor>
</mvc:interceptors>
重新部署Tomcat
控制台生成
那么我们再试一下排除拦截的地址
同代码中地址一致
发现控制台没有拦截信息了
这里注意一下/*只能表示下一级目录的跳转受到拦截器的拦截
我们输入个下一级地址看一下效果
http://localhost:8080/SpringMVC/ab
展示结果如下
后台输出了三个拦截器的语句
那如果我们设置多级目录呢,看一下
控制台没有输出
那该怎么办呢,原来我们需要配置成/**
修改springmvc.xml
<mvc:mapping path="/**"/>
再重新部署一下Tomcat
前台展示不变
后台输出了三个拦截器
11.3多个拦截器的执行顺序
配置springmvc.xml
<!-- 配置拦截器 -->
<mvc:interceptors>
<ref bean="firstInterceptor"></ref>
</mvc:interceptors>
重新Tomcat,加上断点看DispatcherServlet.class源码
preHandle拦截器
postHandle拦截器
afterCompletion拦截器位置
创建多个拦截器
创建控制层SecondInterceptor.java
package com.atguigu.interceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @ClassName: FirstInterceptor
* @Description:
* @Author: wty
* @Date: 2023/2/1
*/
@Component
public class SecondInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("SecondInterceptor.preHandle");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("SecondInterceptor.postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("SecondInterceptor.afterCompletion");
}
}
配置springmvc.xml
<ref bean="secondInterceptor"></ref>
拦截器配置如下:
这里2个控制层的拦截器preHandle()都返回true
(1).若每个拦截器的preHandle()都返回true
重新部署Tomcat,点击测试拦截器
跳转成功
看后台的控制台
交换一下配置拦截器的顺序
重新部署Tomcat,点击拦截器
跳转成功
看一下控制台
得出以下结论
总结
此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:
preHandle()会按照配置的顺序执行,
而postHandle()和afterCompletion()会按照配置的反序执行。
为什么会这样呢,我们看一下源码
进入断点
preHandle正序执行的源码
postHandle逆序执行源码
afterCompletion逆序执行原因
(2).若某个拦截器的preHandle()返回了false
修改SecondInterceptor.java的preHandle方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("SecondInterceptor.preHandle");
return false;
}
}
修改springmvc.xml的顺序
<ref bean="firstInterceptor"></ref>
<ref bean="secondInterceptor"></ref>
重新部署Tomcat,输出结果
原因:
此时SecondInterceptor没有加入interceptorList中,紧接着看triggerAfterCompletion方法
总结
若某个拦截器的preHandle()返回了false。
preHandle()返回false和它之前的拦截器的preHandle()都会执行,拦截器中所有的postHandle()都不执行,拦截器的preHandle()返回false之前的拦截器的afterCompletion()会执行。
(3).若所有拦截器的preHandle()都返回了false
修改FirstInterceptor.java
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor.preHandle");
return false;
}
重新部署Tomcat,发现后台输出FirstInterceptor的preHandle,也很好理解,源码只执行了FirstInterceptor的preHandle后就返回了
12.异常处理器
12.1基于配置的异常处理
SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver。
看一下接口的源码:
public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(HttpServletRequest var1, HttpServletResponse var2, @Nullable Object var3, Exception var4);
}
HandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver和
SimpleMappingExceptionResolver
看一下类图的关系
SpringMVC提供了自定义的异常处理器SimpleMappingExceptionResolver,使用方式如下
首先注释掉springmvc.xml中的拦截器配置
紧接着配置异常处理解析器
修改springmvc.xml
<!-- 异常处理解析器 -->
<bean id="SimpleMappingExceptionResolver"
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!-- error是逻辑视图, key设置要处理的异常,value设置出现该异常时要跳转的页面所对应的逻辑视图 -->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
</bean>
新建error.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>错误</title>
</head>
<body>
<h1>error.html</h1>
</body>
</html>
修改TestController.java
@RequestMapping("/test/hello")
public String testHello() {
System.out.println(1 / 0);
return "success";
}
重新部署Tomcat
直接填写地址
http://localhost:8080/SpringMVC/test/hello
发现直接跳转到我们配置的错误页面
我们看一下源码SimpleMappingExceptionResolver.class
这个返回类型是ModelAndView
我们跳转error相当于只是设置了视图,那么共享域呢,我们继续修改
springmvc.xml
<!-- 设置共享在请求域中的异常信息的属性名 -->
<property name="exceptionAttribute" value="ex"></property>
修改error.html
<p th:text="${ex}"></p>
重新部署Tomcat,发现错误信息也展示在页面上了
12.2基于注解的异常处理
先注释springmvc.xml中的xml配置
新增控制层ExceptionController.java
代码如下
增加@ControllerAdvice注解
ExceptionController.java如下
// 将当前类标识为异常处理的组件
@ControllerAdvice
public class ExceptionController {
// 设置要处理的异常信息
@ExceptionHandler(ArithmeticException.class)
// ex表示控制器方法出现的异常
public ModelAndView handleException(Throwable ex) {
ModelAndView mav = new ModelAndView();
//向请求域共享数据
mav.addObject("ex", ex);
//设置视图,实现页面跳转
mav.setViewName("error");
return mav;
}
}
重新部署Tomcat,发现跳转成功,异常信息成功展示
13.注解配置SpringMVC
使用配置类和注解代替web.xml和SpringMVC配置文件的功能
新建一个Module
Name:spring-mvc-annotation
GroupId:com.atguigu
拷贝上一个工程的pom.xml过来即可
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SSM</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>war</packaging>
<groupId>com.atguigu</groupId>
<artifactId>spring-mvc-annotation</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- ServletAPI -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Spring5和Thymeleaf整合包 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
<!-- 导入jackson的依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
</project>
13.1创建初始化类,代替web.xml
在Servlet3.0环境中,容器会在类路径中查找实现javax.servlet.ServletContainerInitializer接口的类,如果找到的话就用它来配置Servlet容器。 Spring提供了这个接口的实现,名为
SpringServletContainerInitializer,这个类反过来又会查找实现WebApplicationInitializer的类并将配置的任务交给它们来完成。Spring3.2引入了一个便利的WebApplicationInitializer基础实现,名为AbstractAnnotationConfigDispatcherServletInitializer,当我们的类扩展了
AbstractAnnotationConfigDispatcherServletInitializer并将其部署到Servlet3.0容器的时候,容器会自动发现它,并用它来配置Servlet上下文。
新建类com.atguigu.WebInit
package com.atguigu;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.filter.HiddenHttpMethodFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import javax.servlet.Filter;
/**
* @ClassName: WebInit
* @Description:
* @Author: wty
* @Date: 2023/2/1
*/
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* @param
* @return java.lang.Class<?>[]
* @description //设置一个配置类,来代替spring的配置文件
* @date 2023/2/1 18:21
* @author wty
**/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
/**
* @param
* @return java.lang.Class<?>[]
* @description //设置一个类,代替springmvc的配置文件
* @date 2023/2/1 18:21
* @author wty
**/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
/**
* @param
* @return java.lang.String[]
* @description //设置springmvc前端口之前DispatcherServlet的url-pattern
* @date 2023/2/1 18:22
* @author wty
**/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
/**
* @param
* @return javax.servlet.Filter[]
* @description //设置过滤器
* @date 2023/2/1 18:29
* @author wty
**/
@Override
protected Filter[] getServletFilters() {
// 创建编码过滤器
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
// 创建处理请求方式的过滤器
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter, hiddenHttpMethodFilter};
}
}
创建几个配置类
13.2创建SpringConfig配置类,代替spring的配置文件
创建类SpringConfig.java
package com.atguigu;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName: SpringConfig
* @Description:代替spring的配置文件
* @Author: wty
* @Date: 2023/2/1
*/
// 将当前类标识为配置类
@Configuration
public class SpringConfig {
//ssm整合之后,spring的配置信息写在此类中
}
13.3创建WebConfig配置类,代替SpringMVC的配置文件
创建控制层TestController.java,里面内容暂时不用写。
创建拦截器com.atguigu.interceptor.FirstInterceptor
代码如下
@Component
public class FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
@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");
}
}
创建前台界面
创建WebConfig.java
package com.atguigu.config;
import com.atguigu.interceptor.FirstInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import java.util.List;
import java.util.Properties;
/**
* @ClassName: WebConfig
* @Description:代替springmvc的配置文件 扫描组件
* 视图解析器--手动配置xml比较方便
* 默认的servlet
* mvc注解驱动
* 视图控制器
* 文件上传解析器
* 拦截器
* 异常解析器
* @Author: wty
* @Date: 2023/2/1
*/
// 将当前类标识为配置类
@Configuration
// 扫描组件
@ComponentScan("com.atguigu.controller")
// mvc注解驱动
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
/**
* @param
* @return void
* @description //默认的servlet,处理静态资源
* @param: configurer
* @date 2023/2/1 21:28
* @author wty
**/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
/**
* @param
* @return void
* @description //配置视图控制器(首页)
* @param: registry
* @date 2023/2/1 21:54
* @author wty
**/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
}
/**
* @param
* @return org.springframework.web.multipart.commons.CommonsMultipartResolver
* @description //文件上传解析器
* @date 2023/2/1 21:56
* @author wty
**/
// 可以将标识的方法的返回值作为Bean来管理,bean的id为方法的方法名
@Bean
public CommonsMultipartResolver multipartResolver() {
return new CommonsMultipartResolver();
}
/**
* @param
* @return void
* @description //拦截器
* @param: registry
* @date 2023/2/1 22:02
* @author wty
**/
@Override
public void addInterceptors(InterceptorRegistry registry) {
FirstInterceptor firstInterceptor = new FirstInterceptor();
registry.addInterceptor(firstInterceptor).addPathPatterns("/**").excludePathPatterns("/abc");
}
/**
* @param
* @return void
* @description //异常处理解析器
* @param: resolvers
* @date 2023/2/1 22:08
* @author wty
**/
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
properties.setProperty("java.lang.ArithmeticException", "error");
simpleMappingExceptionResolver.setExceptionMappings(properties);
simpleMappingExceptionResolver.setExceptionAttribute("ex");
resolvers.add(simpleMappingExceptionResolver);
}
/**
* @param
* @return org.thymeleaf.templateresolver.ITemplateResolver
* @description //配置生成模板解析器
* @date 2023/2/1 22:22
* @author wty
**/
@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;
}
/**
* @param
* @return org.thymeleaf.spring5.SpringTemplateEngine
* @description //生成模板引擎并为模板引擎注入模板解析器
* @param: templateResolver
* @date 2023/2/1 22:22
* @author wty
**/
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
/**
* @param
* @return org.springframework.web.servlet.ViewResolver
* @description //生成视图解析器并未解析器注入模板引擎
* @param: templateEngine
* @date 2023/2/1 22:21
* @author wty
**/
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
13.4测试功能
配置Tomcat
设置启动项
修改TestController.java
@Controller
public class TestController {
@RequestMapping("/test/hello")
public String testHello() {
System.out.println(1 / 0);
return "success";
}
}
重启Tomcat
单击测试拦截器,成功跳转错误页面
后台控制台输出