# SpringMVC
# 第 1 章 SpringMVC 介绍
# 1.1 SpringMVC 基本介绍
# 1.1.1 SpringMVC 特点及概述
(1)SpringMVC 从易用性、效率上比曾经流行的 Struts2 更好
(2)SpringMVC 是 web 层框架【SpringMVC 接管了 web 层的组件,比如控制器、视图、视图解析、返回给用户的数据格式、同时支持 MVC 的开发模式/开发架构】
(3)SpringMVC 通过注解让 POJO 成为控制器,不需要继承类或者实现接口
(4)SpringMVC 采用低耦合的组件设计方式,具有更好的扩展和灵活性
(5)支持 REST 格式的 URL 请求
(6)SpringMVC 是基于 Spring 的,也就是说 SpringMVC 是在 Spring 基础上的,SpringMVC 的核心包是 spring-webmvc-xx.jar 和 spring-web-xx.jar
# 1.1.2 梳理 Spring、SpringMVC、SpringBoot 的关系
(1)SpringMVC 只是 Spring 处理 web 层请求的一个模块/组件,SpringMVC 的基石是 Servlet
(2)SpringBoot 是为了简化开发者的使用推出的封神框架(约定优于配置,简化了 Spring 的配置流程),SpringBoot 包含很多组件/框架,Spring 就是最核心的内容之一,也包含 SpringMVC
(3)它们的关系大概是:SpringBoot > Spring > SpringMVC
# 1.2 SpringMVC 快速入门
# 1.2.1 需求说明/图解
完成一个最基本的测试案例 - 登录案例
# 1.2.2 SpringMVC 登录流程分析
# 1.2.3 SpringMVC 登录 - 代码实现
(1)创建 SpringMVC Web 工程并配置 Tomcat
(2)导入 SpringMVC 开发需要的 jar 包
(3)创建 src/applicationContext-mvc.xml 文件(就是 Spring 的容器文件),文件名自己定
(4)配置 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">
<!--配置前端控制器/中央控制器/分发控制器
用户的请求都会经过它的处理
-->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置属性
配置属性 contextConfigLocation,指定 DispatcherServlet 去操作的 Spring 配置文件
-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext-mvc.xml</param-value>
</init-param>
<!--在 web 项目启动时,就自动的加载 DispatcherServlet-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<!--
这里把 urlPattern 配置成 /,表示用户的请求都要经过 DispatcherServlet
这样配置也符合 rest 风格的 url 请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
(5)创建 login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
</head>
<body>
<h3>登录页面</h3>
<form action="?">
u:<input name="username" type="text"> <br/>
p:<input name="password" type="password"> <bt/>
<input type="submit" value="登录">
</form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
(6)创建 Servlet
/**
* @Author: 止束
* @Version: 1.0
* @DateTime: 2024/6/23 15:06
* @Description: (1) 如果我们使用了 SpringMVC,在一个类上标识 @Controller,表示将该类视为一个控制器注入到容器
*/
@Controller
public class UserServlet {
//编写方法响应用户的请求
/**
* (1) login() 方法用于响应用户的登录请求
* (2) @RequestMapping(value = "/login") 类似我们以前在原生的 Servlet 中配置的 url-pattern
* (3) 即当用户在浏览器输入 http://localhost:8080/web工程路径/login 就能访问到 login() 方法
* (4) return "login_ok"; 表示返回结果给视图解析器(InternalResourceViewResolver),视图解析器会根据配置来决定跳转到哪个页面
* */
@RequestMapping(value = "/login")
public String login() {
System.out.println("login ok...");
return "login_ok";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(7)创建 login_ok.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录成功</title>
</head>
<body>
<h1>恭喜,登录成功</h1>
</body>
</html>
2
3
4
5
6
7
8
9
(8)配置 applicationContext-mvc.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"
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">
<!--配置要自动扫描的包-->
<context:component-scan base-package="com.hspedu.web"/>
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置属性 suffix(前缀) 和 prefix(后缀)-->
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
补充说明
/**
* (1) login() 方法用于响应用户的登录请求
* (2) @RequestMapping(value = "/login") 类似我们以前在原生的 Servlet 中配置的 url-pattern
* (3) 即当用户在浏览器输入 http://localhost:8080/web工程路径/login 就能访问到 login() 方法
* (4) return "login_ok"; 表示返回结果给视图解析器(InternalResourceViewResolver),
* 视图解析器会根据配置来决定跳转到哪个页面,比如有如下配置
*
* <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
* <property name="prefix" value="/WEB-INF/pages/"/>
* <property name="suffix" value=".jsp"/>
* </bean>
* 根据上面的配置,当 return "login_ok" 就是转发到 /WEB-INF/pages/login_ok.jsp
* */
@RequestMapping(value = "/login")
public String login() {
System.out.println("login ok...");
return "login_ok";
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(9)配置 Tomcat
# 1.2.4 注意事项和细节说明
(1)这里的 UserServlet 需要注解成 @Controller,我们称为一个 Handler 处理器
(2)UserServlet 在指定 Url 时,可以这样写 @RequestMapping("/login"),即把 value= 省略
(3)关于 SpringMVC 的 DispatcherServlet 的配置文件,如果不在 web.xml 指定 applicationContext-mvc.xml,默认在 /WEB-INF/springDispatcherServlet-servlet.xml 找这个配置文件(其中的名字 applicationContext-mvc 和 springDispatcherServlet 都是自己取得,-servlet 是系统要求有的),看下源码
修改其位置步骤:
1)修改 web.xml,注销 init-param 的配置
2)剪切原 applicationContext-mvc.xml 到 /WEB-INF 目录下,文件名为 你配置的 DispatchServlet 的 <servlet-name> + -servlet.xml
,比如:/WEB-INF/springDispatcherServlet-servlet.xml
# 第 2 章 SpringMVC 执行流程
# 2.1 图解
# 第 3 章 @RequestMapping
# 3.1 基本使用
@RequestMapping 注解可以指定控制器/处理器的某个方法的请求的 url,基本用法前面已经写过了
# 3.2 @RequestMapping 注解其它使用方式
# 3.2.1 @RequestMapping 可以修饰方法和类
(1)@RequestMapping 注解可以修饰方法,还可以修饰类,当同时修饰类和方法时,请求的 url 就是组合: /类请求值/方法请求值
web/request.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>购买商品</title>
</head>
<body>
<h1>购买商品</h1>
<form action="user/buy" method="post">
购买人: <input type="text" name="username"> <br/>
购买量: <input type="text" name="nums"> <br/>
<input type="submit" value="购买">
</form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
src/com/hspedu/web/UserHandler.java
@RequestMapping(value = "/user")
@Controller //UserHandler 就是一个处理器/控制器,注入到容器
public class UserHandler {
/**
* 当类上有注解 @RequestMapping(value = "/user"),方法上也有注解 @RequestMapping(value = "/buy") 时,
* buy() 方法请求的 url: http://localhost:8080/工程路径/user/buy
*
* method = RequestMethod.POST 表示请求 buy() 方法的请求必须是 post 请求
* RequestMethod 有四个常用选项:post、get、put、delete
* SpringMVC 控制器默认支持 get 和 post 两种方式
* */
@RequestMapping(value = "/buy", method = RequestMethod.POST)
public String buy() {
System.out.println("购买商品");
return "success";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
web/WEB-INF/pages/success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>操作成功</title>
</head>
<body>
<h1>恭喜,操作成功</h1>
</body>
</html>
2
3
4
5
6
7
8
9
# 3.2.2 @RequestMapping 可以指定请求方式
(1)@RequestMapping 可以指定请求的方式(post、get、put、delete),如果指定了请求方式,那么请求时的方式要和指定的一样,否则报错
(2)SpringMVC 控制器默认支持 get 和 post 两种方式,也就是当不指定 method 时,可以接收 get 和 post 请求
(3)当明确指定了 method,则需要按指定的方式请求,否则会报错
# 3.2.3 @RequestMapping 可以指定 params 和 headers 即支持简单表达式
(1)param1:表示请求必须包含名为 param1 的请求参数
(2)!=param1:表示请求不能包含名为 param1 的请求参数
(3)param1 != value1:表示请求包含名为 param1 的请求参数,但其值不能为 value1
(4){"param1 = value1", "param2"}:请求必须包含名为 param1 和 param2 的两个请求参数,且 param1 参数的值必须为 value1
(5)在 UserHandler.java 中增加方法
web/request.jsp
<hr><h1>演示 params 的使用</h1>
<a href="user/find?bookId=100">查询书籍</a>
2
src/com/hspedu/web/UserHandler.java
/**
* params = "bookId" 表示请求该目标方法时,必须给一个 bookId 参数,其值不做限制
* 形参中的 String bookId:表示请求目标方法时,假如 url 中携带参数 bookId = 100,那么就会把 100 赋给形参 bookId,注意形参中的 bookId 和 url 中的 bookId 是对应的
* */
@RequestMapping(value = "/find", params = "bookId", method = RequestMethod.GET)
public String search(String bookId) {
System.out.println("查询书籍 bookId = " + bookId);
return "success";
}
2
3
4
5
6
7
8
9
web/WEB-INF/pages/success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>操作成功</title>
</head>
<body>
<h1>恭喜,操作成功</h1>
</body>
</html>
2
3
4
5
6
7
8
9
(6)也可以指定让请求中必须包含参数名和参数值,比如
@RequestMapping(value = "/find", params = "bookId=100", method = RequestMethod.GET)
这样就必须要有 bookId 参数并且值为 100,否则报错
(7)需要有 bookId 参数,并且值不为 100,否则报错
@RequestMapping(value = "/find", params = "bookId!=100", method = RequestMethod.GET)
# 3.2.4 @RequestMapping 支持 Ant 风格资源地址
(1)?
:匹配文件名中的一个字符
(2)*
:匹配文件名中的任意字符
(3)**
:匹配多层路径
(4)Ant 风格的 url 地址举例
/user/*/createUser
:匹配 /user/aaa/createUser
、/user/bbb/createUser
等 URL
/user/**/createUser
:匹配 /user/createUser
、/user/aaa/bbb/createUser
等 URL
/user/createUser??
:匹配 /user/createUseraa
、/user/createUserbb
等 URL
(5)UserHandler 中增加方法 im
web/request.jsp
<hr> <h1>演示 Ant 风格的请求资源方式</h1>
<a href="user/message/aa">发送消息1</a> <br/>
<a href="user/message/aa/bb/cc">发送消息2</a> <br/>
2
3
src/com/hspedu/web/UserHandler.java
/**
* 要求可以配置 /user/message/aa, /user/message/aa/bb/cc
* */
@RequestMapping(value = "/message/**")
public String im() {
System.out.println("发送消息");
return "success";
}
2
3
4
5
6
7
8
web/WEB-INF/pages/success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>操作成功</title>
</head>
<body>
<h1>恭喜,操作成功</h1>
</body>
</html>
2
3
4
5
6
7
8
9
# 3.2.5 @RequestMapping 可配合 @PathVariable 映射 URL 绑定的占位符
(1)@RequestMapping 还可以配合 @PathVariable 映射 URL 绑定的占位符
(2)这样就不需要在 url 地址上带参数名了,更加的简洁明了
比如:我们的前端页面是这样的,kristina 和 300 是参数值
<hr><h1>占位符的演示</h1>
<a href="user/reg/kristina/300">占位符的演示</a>
2
(3)代码实现
src/com/hspedu/web/UserHandler.java
/**
* 我们希望目标方法获取到 username 和 userid,value="/xx/{username}" - @PathVariable("username")
* 前端页面: <a href="user/reg/kristina/300">占位符的演示</a>
* (value = "/reg/{username}/{userid}"):表示 kristina 对应 {username},300 对应 {userid}
* */
@RequestMapping(value = "/reg/{username}/{userid}")
public String register(@PathVariable("username") String name, @PathVariable("userid") String id) {
System.out.println("接收到的参数-- " + "username = " + name + " -- " + "userid = " + id); //接收到的参数-- username = kristina -- userid = 300
return "success";
}
2
3
4
5
6
7
8
9
10
web/request.jsp
<hr> <h1>占位符的演示</h1>
<a href="user/reg/kristina/300">占位符的演示</a>
2
web/WEB-INF/pages/success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>操作成功</title>
</head>
<body>
<h1>恭喜,操作成功</h1>
</body>
</html>
2
3
4
5
6
7
8
9
# 3.2.6 注意事项和使用细节
(1)映射的 URL 不能重复,即不能重复映射 URL
(2)各种请求的简写形式
1)@RequestMapping(value = "/buy", method = RequestMethod.POST)
等价 @PostMapping(value = "/buy")
简写方式一览:@GetMapping、@PostMapping、@PutMapping、@DeleteMapping
(3)如果我们确定表单或者超链接会提交某个字段数据比如(email),要求提交的参数名和目标方法的参数名保持一致,比如:输入 localhost:9998/user/hello3?email=tom@sohu.com
,一定要注意提交的参数名和后台方法的形参名保持一致,否则后端接收不到参数
# 3.3 必学必会技术 - Postman(接口测试工具)
# 3.3.1 Postman 介绍
# 3.3.1.1 Postman 是什么
(1)Postman 是一款功能超级强大的用于发送 Http 请求的测试工具
(2)做 Web 页面开发和测试人员的常用工具
(3)创建和发送任何的 Http 请求(Get、Post、Put、Delete)
# 3.3.2 Postman 快速入门
# 3.3.2.1 需求说明
使用 Postman 向 http://www.baidu.com
发送 get 请求,得到返回的 html 格式的数据
# 3.3.3 Postman 完成 Controller 层测试
# 3.3.3.1 测试使用实例
使用 Postman 完成对前面编写的 UserHandler 方法的请求
(1)完成请求
1)确定请求的地址 url,即 http://localhost:8080/springmvc/user/buy
2)确定请求的方式:Post
3)确定请求的参数/数据:无
4)确定 Headers 有没有特殊的指定:无
# 3.3.3.2 其它说明
# 3.3.3.3 课后作业
# 第 4 章 Rest - 优雅的 Url 请求风格
# 4.1 Rest 的基本介绍
(1)Rest:即 Representational State Transfer。资源表现层状态转化,是目前流行的请求方式,它结构清晰,很多网站采用
(2)Http 协议里面,四个表示操作方式的动词:Get、Post、Put、Delete,它们分别对应四种基本操作:Get 用来获取资源、Post 用来新建资源、Put 用来更新资源、Delete 用来删除资源
(3)传统的请求方法:
getBook?id=1 GET
delete?id=1 GET
update POST
add POST
(4)说明:传统的 url 是通过参数来说明 crud 的类型,Rest 是通过 get/post/put/delete 来说明 crud 的类型
(5)Rest 的核心过滤器
1)当前的浏览器只支持 post/get 请求,因此为了得到 put/delete 的请求方式需要使用 Spring 提供的 HiddenHttpMethodFilter 过滤器进行转换
2)HiddenHttpMethodFilter:浏览器 form 表单只支持 get 和 post 请求,而 delete、put 等 method 并不支持,Spring 添加了一个过滤器,可以将这些请求转换为标准的 http 方法,使得支持 get、post、put 与 delete 请求
3)HiddenHttpMethodFilter 能对 post 请求方式进行转换
4)这个过滤器需要在 web.xml 中配置
# 4.2 Rest 风格的 url - 完成增删改查
# 4.2.1 需求说明
# 4.2.2 Rest 应用案例 - 代码实现
(1)修改 web.xml 添加 HiddenHttpMethodFilter
<!--配置 HiddenHttpMethodFilter
(1) 作用是把以 post 方式提交的 delete 和 put 请求进行转换
(2) 配置 url-pattern 是 /* 表示请求都经过 hiddenHttpMethodFilter 过滤
-->
<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>
2
3
4
5
6
7
8
9
10
11
12
(2)修改 springDispatcherServlet-servlet.xml
<!--加入两个常规配置-->
<!--支持 SpringMVC 的高级功能,比如 JSR303 校验,映射动态请求-->
<mvc:annotation-driven></mvc:annotation-driven>
<!--将 SpringMVC 不能处理的请求,交给 Tomcat 处理,比如 css,js-->
<mvc:default-servlet-handler/>
2
3
4
5
(3)创建 rest.jsp,注意需要引入 JQuery,测试的时候 查询/添加/删除/修改 一个一个的来
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>rest</title>
</head>
<body>
<h3>Rest 风格的 crud 操作案例</h3>
<br><hr>
<h3>Rest 风格的 url 查询书籍[get]</h3>
<a href="?">点击查询书籍</a>
<br><hr>
<h3>Rest 风格的 url 添加书籍[post]</h3>
<form action="?" method="?">
name: <input name="bookName" type="text"> <br>
<input type="submit" value="添加书籍">
</form>
<br><hr>
<h3>Rest 风格的 url 删除书籍[delete]</h3>
<a href="?" id="?">删除指定 id 的书</a>
<br><hr>
<h3>Rest 风格的 url 修改书籍[put]</h3>
<form action="?" method="?">
<input type="submit" value="修改书籍">
</form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
(4)创建 BookHandler.java,处理 rest 风格的请求
(5)总代码
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">
<!--配置前端控制器/中央控制器/分发控制器
用户的请求都会经过它的处理
-->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--配置属性
配置属性 contextConfigLocation,指定 DispatcherServlet 去操作的 Spring 配置文件
-->
<!--<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springDispatcherServlet-servlet.xml</param-value>
</init-param>-->
<!--在 web 项目启动时,就自动的加载 DispatcherServlet-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<!--
这里把 urlPattern 配置成 /,表示用户的请求都要经过 DispatcherServlet
这样配置也符合 rest 风格的 url 请求
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--配置 HiddenHttpMethodFilter
(1) 作用是把以 post 方式提交的 delete 和 put 请求进行转换
(2) 配置 url-pattern 是 /* 表示请求都经过 hiddenHttpMethodFilter 过滤
-->
<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-app>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
springDispatcherServlet-servlet.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">
<!--配置要自动扫描的包-->
<context:component-scan base-package="com.hspedu.web"/>
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置属性 suffix(前缀) 和 prefix(后缀)-->
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!--加入两个常规配置-->
<!--支持 SpringMVC 的高级功能,比如 JSR303 校验,映射动态请求-->
<mvc:annotation-driven></mvc:annotation-driven>
<!--将 SpringMVC 不能处理的请求,交给 Tomcat 处理,比如 css,js-->
<mvc:default-servlet-handler/>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
rest.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>rest</title>
<%--引入 JQuery--%>
<script type="text/javascript" src="script/jquery-3.6.0.min.js"></script>
<script type="text/javascript">
$(function () { //当页面加载完成后,就执行
//给删除超链接绑定一个点击事件
$("#deleteBook").click(function () {
//我们自己定义提交的行为
$("#hiddenForm").attr("action", this.href); //把 id 为 deleteBook 的 href 的属性的值赋给 id 为 hiddenForm 的 action 属性
//给 type = hidden 赋个 value 值,这样提交的话就是 _method = DELETE
$(":hidden").val("DELETE");
$("#hiddenForm").submit();
return false; //改变点击超链接的行为,不再提交
})
})
</script>
</head>
<body>
<h3>Rest 风格的 crud 操作案例</h3>
<br><hr>
<h3>Rest 风格的 url 查询书籍[get]</h3>
<a href="user/book/200">点击查询书籍</a>
<br><hr>
<h3>Rest 风格的 url 添加书籍[post]</h3>
<form action="user/book" method="post">
name: <input name="bookName" type="text"> <br>
<input type="submit" value="添加书籍">
</form>
<br><hr>
<h3>Rest 风格的 url 删除书籍[delete]</h3>
<%--重点解读
(1) 在默认情况下 <a href="user/book/600" id="?">删除指定 id 的书</a> 这个超链接的请求是 get 请求
(2) 怎么样将 get 请求转成 SpringMVC 可以识别的 delete 请求? 就要考虑 HiddenHttpMethodFilter 的机制
(3) 看源码:
public static final String DEFAULT_METHOD_PARAM = "_method";
private static final List<String> ALLOWED_METHODS =
Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
//从这个判断语句可以看出只能把 post 转成 put 或者 delete 请求
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
(4) 从上面的源码可以看到 HiddenHttpMethodFilter 过滤器可以对以 Post 方式提交的 delete、put、patch 进行转换,转换成 SpringMVC 识别的 RequestMethod.DELETE、RequestMethod.PUT、...
(5) 那么又有一个问题,就是 <a href="user/book/600" id="?">删除指定 id 的书</a> 是 get 请求,要把这个 get 请求以 post 的方式提交给后端的 Handler(Servlet),这样过滤器才会生效
(6) 那么这个问题可以通过 JQuery 来处理
--%>
<a href="user/book/600" id="deleteBook">删除指定 id 的书</a>
<form action="" method="post" id="hiddenForm">
<input type="hidden" name="_method"/>
</form>
<br><hr>
<h3>Rest 风格的 url 修改书籍[put]</h3>
<form action="user/book/666" method="post">
<input type="hidden" name="_method" value="PUT">
<input type="submit" value="修改书籍">
</form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
BookHandler.java
/**
* @Author: 止束
* @Version: 1.0
* @DateTime: 2024/6/24 12:57
* @Description: BookHandler 处理 rest 风格的请求 - 增删改查
*/
@RequestMapping("/user")
@Controller
public class BookHandler {
//查询 [Get]
@RequestMapping(value = "/book/{id}", method = RequestMethod.GET)
public String getBook(@PathVariable("id") String id) {
System.out.println("查询书籍 id = " + id);
return "success";
}
//添加 [Post]
@PostMapping(value = "/book")
public String addBook(String bookName) {
System.out.println("添加书籍 bookName = " + bookName);
return "success";
}
//删除[delete]
@RequestMapping(value = "/book/{id}", method = RequestMethod.DELETE)
public String delBook(@PathVariable("id") String id) {
System.out.println("删除的书籍的 id = " + id);
//return "success";
//这里使用重定向
//"redirect:/user/success" 会被解析成 /springmvc/user/success
return "redirect:/user/success";
}
//如果请求是 /user/success,就转发到 success.jsp
@RequestMapping(value = "/success")
public String successGenecal() {
return "success"; //由该方法转发到 success.jsp 页面
}
//修改 [PUT]
@PutMapping(value = "/book/{id}")
public String updateBook(@PathVariable("id") String id) {
System.out.println("修改书籍 id = " + id);
return "redirect:/user/success";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 4.2.3 注意事项和细节说明
(1)HiddenHttpMethodFilter 在将 post 转成 delete、put 请求时,是按 _method 为参数名来读取的
(2)如果 Web 项目是运行在 Tomcat8 及以上的版本,会发现被过滤成 DELETE 和 PUT 的请求在到达控制器时能顺利执行,但是返回时(forward)会报 HTTP 405 的错误提示,解决方案:将请求转发(forward)改为请求重定向(redirect),重定向是重定向到一个 Handler(Servlet),由 Handler 转发到页面
(3)上面的代码在进行测试时,如果出现点击修改书籍,仍然走的是删除的 url,是因为浏览器的缓存等原因,换成 Chrome 即可
# 第 5 章 SpringMVC 映射请求数据
# 5.1 获取参数值
# 5.1.1 说明
(1)开发中,如何获取到 http://xxx/url?参数名=参数值&参数名=参数值
中的 参数名=参数值,如果后端的形参和前端的参数名没有保持一致怎么办?
# 5.1.2 代码实现
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试 request parameter</title>
</head>
<body>
<h2>获取到超链接参数值</h2>
<hr/>
<a href="vote/vote01?name=hsp">获取超链接的参数</a>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
@RequestMapping("/vote")
@Controller
public class VoteHandler {
/**
* 获取到超链接传递的数据
* @RequestParam 表示会接收提交的参数
* value = "name" 表示提交的参数名是 name,这个 name 是前端的参数名
* required = false 表示该参数可以没有,如果是默认的 true,则表示必须有这个参数
* 当使用了 @RequestParam(value = "name", required = false) 后请求的参数名和 Handler 的方法的形参名可以不一致
*/
@RequestMapping(value = "/vote01")
public String test01(@RequestParam(value = "name", required = false) String username) {
System.out.println("得到的 username = " + username);
//返回到一个结果
return "success";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 5.2 获取 Http 请求消息头
# 5.2.1 说明
(1)在开发中,如何获取到 http 请求的消息头信息
(2)使用较少,了解即可
# 5.2.2 代码实现
<h1>获取到消息头</h1>
<hr>
<a href="vote/vote02">获取 http 消息头信息</a>
2
3
/**
* 需求:获取 http 请求头信息,获取到 Accept-Encoding 和 Host
* @RequestHeader("Http请求头字段")
*/
@RequestMapping(value = "/vote02")
public String test02(@RequestHeader("Accept-Encoding") String ae, @RequestHeader("Host") String host) {
System.out.println("Accept-Encoding = " + ae);
System.out.println("Host = " + host);
//返回到一个结果
return "success";
}
2
3
4
5
6
7
8
9
10
11
# 5.3 获取 JavaBean 形式的数据
# 5.3.1 使用场景说明
在开发中,如何获取到 JavaBean 的数据,就是以前的 entity/pojo 对象数据,即前端的数据经过 Handler 处理直接封装成 JavaBean 对象
# 5.3.2 代码实现
JavaBean:
public class Pet {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Pet{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Master {
private Integer id;
private String name;
private Pet pet; //对象的属性是另外一个对象[涉及到级联]
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Pet getPet() {
return pet;
}
public void setPet(Pet pet) {
this.pet = pet;
}
@Override
public String toString() {
return "Master{" +
"id=" + id +
", name='" + name + '\'' +
", pet=" + pet +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 演示如何获取到提交的数据,并把获取到的数据封装成 java 对象
* 想要把得到的数据封装成什么类型的 JavaBean 对象,就把这个类型写在方法的形参中,SpringMVC 会自动封装
* 如果想要正确的自动的完成封装,要求提交的数据的参数名要和对应类型的对象的属性名保持一致
* 如果对应类型的对象中有个属性是对象,则指定参数名时可以通过 字段名.字段名 来指定,如 pet.id
* 如果提交的数据的参数名和对象的属性名不匹配,则对象的属性值就是 null
*/
@RequestMapping(value = "/vote03")
public String test03(Master master) {
System.out.println("master = " + master);
//返回结果
return "success";
}
2
3
4
5
6
7
8
9
10
11
12
13
<hr>
<h1>添加主人信息</h1>
<!--
(1) 此表单的数据对应 Master 对象
(2) 提交的数据参数名和对象的属性名要保持一致
-->
<form action="vote/vote03" method="post">
主人号:<input type="text" name="id"><br>
主人名:<input type="text" name="name"><br>
宠物号:<input type="text" name="pet.id"><br>
宠物名:<input type="text" name="pet.name"><br>
<input type="submit" value="添加主人和宠物">
</form>
2
3
4
5
6
7
8
9
10
11
12
13
# 5.3.3 使用注意事项
(1)支持级联数据获取
(2)前端参数名需要和 JavaBean 对象的属性名保持一致
# 5.4 获取原生 Servlet API
# 5.4.1 应用实例
(1)在开发中,我们可能需要使用到原生态的 Servlet API,那么该如何获取
(2)使用 Servlet API,需要引入 Tomcat/lib 下的 servlet-api.jar
# 5.4.2 代码实现
<hr>
<h1>演示 servlet api 的使用</h1>
<form action="vote/vote04" method="post">
用户名: <input type="text" name="username"><br>
密 码: <input type="text" name="pwd"><br>
<input type="submit" value="添加用户">
</form>
2
3
4
5
6
7
/**
* 获取 servlet api 来获取提交的数据
*/
@RequestMapping(value = "/vote04")
public String test04(HttpServletRequest request, HttpServletResponse response) {
String username = request.getParameter("username");
String pwd = request.getParameter("pwd");
System.out.println("username = " + username);
System.out.println("pwd = " + pwd);
//返回结果
return "success";
}
2
3
4
5
6
7
8
9
10
11
12
# 5.4.3 使用注意事项
(1)除了 HttpServletRequest,HttpServletResponse 还可以获取其它对象:HttpSession、java.security.Principal、InputStream、OutputStream、Reader、Writer 等
(2)其中一些对象也可以通过 HttpServletRequest/HttpServletResponse 对象获取,比如 Session 对象,既可以通过参数传入,也可以通过 request.getSession() 获取,效果一样,推荐使用参数形式传入,更加简单明了
# 第 6 章 模型数据
# 6.1 数据放入 Request
# 6.1.1 需求分析
(1)开发中,如何将控制器/处理器中获取到的数据放入到 request 域中,然后在前端(vue/jsp/...)中取出显示
(2)应用实例
# 6.1.2 方式1:默认机制放入 request 域
JavaBean
public class Master {
private Integer id;
private String name;
private Pet pet; //对象的属性是另外一个对象[涉及到级联]
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Pet getPet() {
return pet;
}
public void setPet(Pet pet) {
this.pet = pet;
}
@Override
public String toString() {
return "Master{" +
"id=" + id +
", name='" + name + '\'' +
", pet=" + pet +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
model_data.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试模型数据</title>
</head>
<body>
<h1>添加主人信息</h1>
<form action="vote/vote05" method="post">
主人号: <input type="text" name="id"><br>
主人名: <input type="text" name="name"><br>
宠物号: <input type="text" name="pet.id"><br>
宠物名: <input type="text" name="pet.name"><br>
<input type="submit" value="添加主人和宠物">
</form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
VoteHandler.java
/**
* 演示将提交的数据自动封装到 JavaBean 对象中,SpringMVC 会自动的将其放入到 request 域中
* 这样我们就可以在跳转到的页面中取出数据了
* */
@RequestMapping(value = "/vote05")
public String test05(Master master) {
/*
* SpringMVC 会自动把获取的 model 模型放入到 request 域中,名字就是对应的 JavaBean 的名字,即 ("master", master)
* 也可以手动将 master 放入到 request
* */
//返回到一个结果
return "vote_ok";
}
2
3
4
5
6
7
8
9
10
11
12
13
vote_ok.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>vote_ok</title>
</head>
<body>
<h1>获取到的数据显示页面</h1>
<hr>
取出 request 域的数据
<br>
address: <br>
主人名字 = ${requestScope.master.name}
主人 id = ${requestScope.master.id}
宠物名字 = ${requestScope.master.pet.name}
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 6.1.3 方式2:通过 HttpServletRequest 放入 request 域
/**
* 演示将提交的数据自动封装到 JavaBean 对象中,SpringMVC 会自动的将其放入到 request 域中
* 这样我们就可以在跳转到的页面中取出数据了
* */
@RequestMapping(value = "/vote05")
public String test05(Master master, HttpServletRequest request) {
/*
* SpringMVC 会自动把获取的 model 模型放入到 request 域中,名字就是对应的 JavaBean 的类名的首字母小写,即 ("master", master)
* 也可以手动将 master 放入到 request
* */
//手动给 request 域添加数据
request.setAttribute("address", "beijing");
//如果我们希望修改 master 的 name 的属性值,可以调用 setter 方法
master.setName("nono");
//返回到一个结果
return "vote_ok";
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 6.1.4 方式3:通过请求的方法参数 Map<String, Object> 放入 request 域
/**
* 演示通过 Map<String, Object> 设置数据到 request 域
* */
@RequestMapping(value = "/vote06")
public String test06(Master master, Map<String, Object> map) {
//需求是通过 map 对象,添加属性到 request 域中
//SpringMVC 会遍历 map,然后将 map 的 k-v,存放到 request 域中
map.put("address", "beijing...");
//如果这样写 map.put("master", null); 那么在 SpringMVC 遍历 map 时会将通过默认方式存储在 request 域中的 master 置为空
map.put("master", null);
//返回一个结果
return "vote_ok";
}
2
3
4
5
6
7
8
9
10
11
12
13
# 6.1.5 方式4:通过返回 ModelAndView 对象实现 request 域数据
<br/><hr/>
<h1>添加主人信息[测试 ModelAndView]</h1>
<form action="vote/vote07" method="post">
主人号: <input type="text" name="id"><br>
主人名: <input type="text" name="name"><br>
宠物号: <input type="text" name="pet.id"><br>
宠物名: <input type="text" name="pet.name"><br>
<input type="submit" value="添加主人和宠物">
</form>
2
3
4
5
6
7
8
9
/**
* 演示通过返回 ModelAndView 对象,将数据放入到 request 域
* */
@RequestMapping(value = "/vote07")
public ModelAndView test07(Master master) {
ModelAndView modelAndView = new ModelAndView();
//放入属性到 modelAndView
modelAndView.addObject("address", "shanghai");
//这里指定跳转的视图名称
modelAndView.setViewName("vote_ok");
//返回结果
return modelAndView;
}
2
3
4
5
6
7
8
9
10
11
12
13
# 6.1.6 使用注意事项
(1)从本质看,请求响应的方法 return "xx"
,是返回了一个字符串,其实本质是返回了一个 ModelAndView 对象,只是默认被封装起来的
(2)ModelAndView 既可以包含 model 数据,也可以包含视图信息
(3)ModelAndView 对象的 addObject 方法可以添加 key-val 数据,默认在 request 域中
(4)ModelAndView 对象的 setView 方法可以指定视图名称
# 6.2 数据放入 Session
# 6.2.1 需求分析
开发中,控制器/处理器中获取的数据如何放入 Session 域,然后在前端(vue/jsp/...)取出显示
# 6.2.2 代码实现
<br/><hr/>
<h1>添加主人信息[测试 session]</h1>
<form action="vote/vote08" method="post">
主人号: <input type="text" name="id"><br>
主人名: <input type="text" name="name"><br>
宠物号: <input type="text" name="pet.id"><br>
宠物名: <input type="text" name="pet.name"><br>
<input type="submit" value="添加主人和宠物">
</form>
2
3
4
5
6
7
8
9
/**
* 演示如何将数据设置到 Session 域中
* */
@RequestMapping(value = "/vote08")
public String test08(Master master, HttpSession httpSession) {
//master 对象是默认放在 request 域中的
//这里我们将 master 对象放入到 session 域
httpSession.setAttribute("master", master);
httpSession.setAttribute("address", "guangzhou");
return "vote_ok";
}
2
3
4
5
6
7
8
9
10
11
<hr>
取出 session 域的数据 <br>
address: ${sessionScope.address} <br>
主人名字 = ${sessionScope.master.name}
主人信息 = ${sessionScope.master}
2
3
4
5
# 6.3 @ModelAttribute 实现 prepare 方法
# 6.3.1 应用实例
(1)开发中,有时需要使用某个前置方法(比如 prepareXxx(),方法名由程序员定)给目标方法准备一个模型对象
(2)@ModelAttribute 注解可以实现这样的需求
(3)在某个方法上,增加了 @ModelAttribute 注解后,那么在调用该 Handler 的任何一个方法时,都会先调用这个方法
/**
* (1) 当 Handler 的方法被 @ModelAttribute 标识,那么该方法就被视为一个前置方法
* (2) 当调用该 Handler 的其它的方法时,都会先执行该前置方法
* (3) 类似前面讲解 Spring AOP 中的前置通知
* */
@ModelAttribute
public void prepareModel() {
System.out.println("prepareModel() --- 完成准备工作");
}
2
3
4
5
6
7
8
9
# 6.3.2 @ModelAttribute 最佳实践
(1)修改用户信息流程如下:
1)在修改前,在前置方法中从数据库查出这个用户
2)在修改方法中,可以使用前置方法从数据库查询用户
3)如果表单中对用户的某个属性修改了,则以新的数据为准,如果没有修改,则以数据库的信息为准,比如:用户的某个属性不能修改,就保持原来的值
# 第 7 章 视图和视图解析器
# 7.1 基本介绍
(1)在 SpringMVC 中的目标方法最终返回都是一个视图(有各种视图)
(2)返回的视图都会由一个视图解析器来处理(视图解析器有很多种)
这是之前配置的视图解析器
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--配置属性 suffix(前缀) 和 prefix(后缀)-->
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
</bean>
2
3
4
5
6
# 7.2 自定义视图
# 7.2.1 为什么需要自定义视图
(1)在默认情况下,我们都是返回默认的视图,然后把返回的视图交由 SpringMVC 的 InternalResourceViewResolver 视图解析器来处理的
(2)在实际开发中,我们有时需要自定义视图,这样可以满足更多更复杂的需求
# 7.2.2 自定义视图实例 - 代码实现
(1)配置 springDispatcherServlet-servlet.xml,增加自定义视图解析器
view.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>自定义视图测试</title>
</head>
<body>
<h1>自定义视图测试</h1>
<a href="goods/buy">点击到自定义视图</a>
</body>
</html>
2
3
4
5
6
7
8
9
10
GoodsHandler.java
@RequestMapping("/goods")
@Controller
public class GoodsHandler {
@RequestMapping(value = "/buy")
public String buy() {
System.out.println("------buy()-----");
return "hspView"; //hspView 是自定义视图名
}
}
2
3
4
5
6
7
8
9
springDispatcherServlet-servlet.xml 配置自定义视图解析器
<!--
配置自定义视图解析器 BeanNameViewResolver
BeanNameViewResolver 可以去解析我们自定义的视图
配置的属性 order,表示视图解析器执行的顺序,值越小优先级越高
属性 order 的默认值是最低优先级,值为 Integer.MAX_VALUE
-->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="99"/>
</bean>
2
3
4
5
6
7
8
9
MyView.java 自定义视图
/**
* (1) MyView 继承了 AbstractView,就可以作为一个视图使用
* (2) @Component(value = "hspView") 注解将该视图注入到容器中
*/
@Component(value = "hspView")
public class MyView extends AbstractView {
@Override
protected void renderMergedOutputModel(Map<String, Object> map, HttpServletRequest request, HttpServletResponse response) throws Exception {
//完成视图渲染
//确定我们要跳转的页面[请求转发] /WEB-INF/pages/my_view.jsp
System.out.println("进入到自己的视图...");
/*
* 下面这个就是进行请求转发到 /WEB-INF/pages/my_view.jsp
* /WEB-INF/pages/my_view.jsp 会被 SpringMVC 解析成 /springmvc/WEB-INF/pages/my_view.jsp
* */
request.getRequestDispatcher("/WEB-INF/pages/my_view.jsp").forward(request, response);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
my_view.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>my_view页面</title>
</head>
<body>
<h1>进入到 my_view 页面</h1>
<p>是从自定义的视图来的</p>
</body>
</html>
2
3
4
5
6
7
8
9
10
# 7.2.3 自定义视图工作流程
# 7.2.3.1 自定义视图步骤
(1)自定义视图:创建一个 View 的 bean,该 bean 需要继承自 AbstractView,并实现 renderMergedOutputModel 方法,并把自定义 View 加入到 IOC 容器中
(2)自定义视图的视图处理器,使用 BeanNameViewResolver,这个视图处理器也需要配置到 IOC 容器
(3)BeanNameViewResolver 的调用优先级需要设置一下,设置 order 比 Integer.MAX_VALUE 小的值,以确保其在 InternalResourceViewResolver 之前被调用,InternalResourceViewResolver 是 SpringMVC 默认的视图解析器
# 7.2.3.2 自定义视图解析器执行流程
(1)SpringMVC 调用目标方法(Handler),返回自定义 View 在 IOC 容器中的 id,即 hspView
(2)SpringMVC 调用 BeanNameViewResolver 解析视图:从 IOC 容器中获取返回 id 值对应的 bean,即自定义的 View 的对象,即 MyView,别名是 hspView
(3)SpringMVC 调用自定义视图的 renderMergedOutputModel 方法渲染视图
注意:如果在 SpringMVC 调用目标方法,返回自定义 View 在 IOC 容器中的 id 不存在,则仍然按照默认的视图处理器机制处理
# 7.2.3.3 默认视图解析器执行流程
默认的视图解析器执行流程就是拼接,把目标方法中的 return "hspView";
拼接成 /WEB-INF/pages/hspView.jsp
然后就会直接在相应目录下找到 hspView.jsp 页面进行渲染
# 7.2.3.4 自定义视图解析器和默认视图解析器的执行关系
当自定义视图解析器的优先级高于默认的视图解析器的优先级时,会先执行自定义视图解析器,当自定义的 View 不存在时,会走默认的视图解析器
当自定义视图解析器的优先级低于默认的视图解析器的优先级时,会先执行默认视图解析器,当默认视图解析器没有解析到自定义的视图时,会直接报错而不会再执行自定义解析器
# 7.3 目标方法直接指定转发或重定向
# 7.3.1 使用实例
(1)默认返回的方式是请求转发,然后用视图处理器进行处理,比如在目标方法中这样写
(2)也可以在目标方法直接指定重定向或转发的 url 地址
(3)如果指定重定向,不能定向到 /WEB-INF 目录中
/**
* 演示直接指定要请求转发的或者是重定向的页面
* */
@RequestMapping(value = "/order")
public String order() {
System.out.println("=====order()=====");
//请求转发到 /WEB-INF/pages/my_view.jsp
//下面的 /WEB-INF/pages/my_view.jsp 会被解析成 /springmvc/WEB-INF/pages/my_view.jsp
//return "forward:/WEB-INF/pages/my_view.jsp";
//直接指定要重定向的页面
//对于重定向来说,不能重定向到 /WEB-INF 目录下
//下面的 /login.jsp 是在服务器解析,解析成 /springmvc/login.jsp
return "redirect:/login.jsp";
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 7.4 作业布置
完成一个简单的用户登录案例
(1)编写登录页面 login.jsp
(2)LoginHandler [doLogin 方法],如果用户输入的用户名是 hsp 密码是 123,就可以登录,否则登录失败
(3)创建 JavaBean:User.java
(4)表单提交数据到 doLogin 方法,以 User 对象形式接收
(5)登录成功到页面 login_ok.jsp 并显示登录欢迎信息
(6)登录失败到页面 login_error.jsp,并给出重新登录的超链接
# 第 9 章 数据格式化
# 9.1 基本介绍
在我们提交数据(比如表单时),SpringMVC 怎样对提交的数据进行转换和处理的
(1)基本数据类型可以和字符串之间自动完成转换,比如:SpringMVC 上下文中内建了很多转换器,可以完成大多数 Java 类型的转换工作
# 9.2 基本数据类型和字符串自动转换
# 9.2.1 页面演示
Monster.java
public class Monster {
private Integer id;
private String email;
private Integer age;
private String name;
public Monster() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Monster{" +
"id=" + id +
", email='" + email + '\'' +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
data_valid.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>SpringMVC[数据格式/验证等]</title>
</head>
<body>
<h1>SpringMVC[数据格式/验证等]</h1>
<hr>
<a href="<%=request.getContextPath()%>/addMonsterUI">添加妖怪</a>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
MonsterHandler.java
/**
* @Author: 止束
* @Version: 1.0
* @DateTime: 2024/6/26 19:01
* @Description: 处理器用来处理响应用户提交的数据
*/
@Controller
@Scope(value = "prototype") //表示每次请求 MonsterHandler 会生成一个新的对象
public class MonsterHandler {
//显示添加 monster 的界面
//Map<String, Object> map : 当我们向 map 中添加数据时,会默认存放到 request
@RequestMapping(value = "/addMonsterUI")
public String addMonsterUI(Map<String, Object> map) {
/*
* 如果你跳转的页面使用了 SpringMVC 的标签,就需要准备一个对象并且放入到 request 域中,
* 这个对象的属性名 monster 对应 SpringMVC 表单标签的 modelAttribute = "monster"
* */
map.put("monster", new Monster());
return "datavalid/monster_addUI";
}
/**
* 编写方法,处理添加妖怪
* (1) SpringMVC 可以将提交的数据按照参数名和对象的属性名匹配
* (2) 直接封装到对象中
* */
@RequestMapping(value = "/save")
public String save(Monster monster) {
System.out.println("-----monster-----" + monster);
return "datavalid/success";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
monster_addUI.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>添加妖怪</title>
</head>
<body>
<h3>添加妖怪</h3>
<%--
这里的表单,我们使用 SpringMVC 的标签来完成
(1) SpringMVC 表单标签在显示之前必须在 request 中有一个 bean,该 bean 的属性和表单标签的字段要对应
(2) request 中的 key 为:form 标签的 modelAttribute 属性值,比如这里的 monster
(3) SpringMVC 的 form:form 标签的 action 属性值中的 / 不代表 WEB 应用的根目录
(4) 这里使用 SpringMVC 的标签的主要目的是方便提示信息回显
--%>
<form:form action="save" method="post" modelAttribute="monster">
妖怪名字: <form:input path="name"/> <br><br>
妖怪年龄: <form:input path="age"/> <br><br>
电子邮件: <form:input path="email"/> <br><br>
<input type="submit" value="添加妖怪"/>
</form:form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>添加成功页面</title>
</head>
<body>
<h1>恭喜,添加成功</h1>
</body>
</html>
2
3
4
5
6
7
8
9
# 9.2.2 Postman 完成测试
# 9.3 特殊数据类型和字符串间的转换
# 9.3.1 应用实例
(1)特殊数据类型和字符串之间的转换使用注解(比如日期,规定格式的小数比如货币形式等)
(2)对于日期和货币可以使用 @DateTimeFormat 和 @NumberFormat 注解,把这两个注解标记在字段上即可
(3)修改 Monster.java,增加 birthday 和 salary 字段
Monster.java
public class Monster {
private Integer id;
private String email;
private Integer age;
private String name;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@NumberFormat(pattern = "###,###.##")
private Float salary;
public Monster() {
}
public Monster(Integer id, String email, Integer age, String name, Date birthday, Float salary) {
this.id = id;
this.email = email;
this.age = age;
this.name = name;
this.birthday = birthday;
this.salary = salary;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Float getSalary() {
return salary;
}
public void setSalary(Float salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Monster{" +
"id=" + id +
", email='" + email + '\'' +
", age=" + age +
", name='" + name + '\'' +
", birthday=" + birthday +
", salary=" + salary +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
data_valid.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>SpringMVC[数据格式/验证等]</title>
</head>
<body>
<h1>SpringMVC[数据格式/验证等]</h1>
<hr>
<a href="<%=request.getContextPath()%>/addMonsterUI">添加妖怪</a>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
MonsterHandler.java
/**
* @Author: 止束
* @Version: 1.0
* @DateTime: 2024/6/26 19:01
* @Description: 处理器用来处理响应用户提交的数据
*/
@Controller
@Scope(value = "prototype") //表示每次请求 MonsterHandler 会生成一个新的对象
public class MonsterHandler {
//显示添加 monster 的界面
//Map<String, Object> map : 当我们向 map 中添加数据时,会默认存放到 request
@RequestMapping(value = "/addMonsterUI")
public String addMonsterUI(Map<String, Object> map) {
/*
* 如果你跳转的页面使用了 SpringMVC 的标签,就需要准备一个对象并且放入到 request 域中,
* 这个对象的属性名 monster 对应 SpringMVC 表单标签的 modelAttribute = "monster"
* */
map.put("monster", new Monster());
return "datavalid/monster_addUI";
}
/**
* 编写方法,处理添加妖怪
* (1) SpringMVC 可以将提交的数据按照参数名和对象的属性名匹配
* (2) 直接封装到对象中
* */
@RequestMapping(value = "/save")
public String save(Monster monster) {
System.out.println("-----monster-----" + monster);
return "datavalid/success";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
monster_addUI.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>添加妖怪</title>
</head>
<body>
<h3>添加妖怪</h3>
<%--
这里的表单,我们使用 SpringMVC 的标签来完成
(1) SpringMVC 表单标签在显示之前必须在 request 中有一个 bean,该 bean 的属性和表单标签的字段要对应
(2) request 中的 key 为:form 标签的 modelAttribute 属性值,比如这里的 monsters
(3) SpringMVC 的 form:form 标签的 action 属性值中的 / 不代表 WEB 应用的根目录
(4) 这里使用 SpringMVC 的标签的主要目的是方便提示信息回显
--%>
<form:form action="save" method="post" modelAttribute="monster">
妖怪名字: <form:input path="name"/> <br><br>
妖怪年龄: <form:input path="age"/> <br><br>
电子邮件: <form:input path="email"/> <br><br>
妖怪生日: <form:input path="birthday"/> 要求以"9999-11-11"的形式 <br><br>
薪水: <form:input path="salary"/> 要求以"123,890.12"的形式 <br><br>
<input type="submit" value="添加妖怪"/>
</form:form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>添加成功页面</title>
</head>
<body>
<h1>恭喜,添加成功</h1>
</body>
</html>
2
3
4
5
6
7
8
9
# 第 10 章 验证及国际化
# 10.1 概述
(1)对输入的数据(比如表单数据),进行必要的验证,并给出相应的提示信息
(2)对于验证表单数据,SpringMVC 提供了很多实用的注解,这些注解由 JSR 303 验证框架提供
(3)JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 中
(4)JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则并通过标准的验证接口对 Bean 进行验证
(5)JSR 303 提供的基本验证注解有:
(6)Hibernate Validator 扩展注解:Hibernate Validator 和 Hibernate 没有关系,只是 JSR 303 实现的一个扩展,Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:
# 10.2 应用实例
引入验证和国际化相关的 jar 包
Monster.java
public class Monster {
private Integer id;
private String email;
@Range(min = 1, max = 100) //表示接收的 age 值在 1 - 100 之间
private Integer age;
@NotEmpty //表示 name 不能为空
private String name;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@NumberFormat(pattern = "###,###.##")
private Float salary;
public Monster() {
}
public Monster(Integer id, String email, Integer age, String name, Date birthday, Float salary) {
this.id = id;
this.email = email;
this.age = age;
this.name = name;
this.birthday = birthday;
this.salary = salary;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Float getSalary() {
return salary;
}
public void setSalary(Float salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Monster{" +
"id=" + id +
", email='" + email + '\'' +
", age=" + age +
", name='" + name + '\'' +
", birthday=" + birthday +
", salary=" + salary +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
data_valid.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>SpringMVC[数据格式/验证等]</title>
</head>
<body>
<h1>SpringMVC[数据格式/验证等]</h1>
<hr>
<a href="<%=request.getContextPath()%>/addMonsterUI">添加妖怪</a>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
MonsterHandler.java
/**
* @Author: 止束
* @Version: 1.0
* @DateTime: 2024/6/26 19:01
* @Description: 处理器用来处理响应用户提交的数据
*/
@Controller
@Scope(value = "prototype") //表示每次请求 MonsterHandler 会生成一个新的对象
public class MonsterHandler {
//显示添加 monster 的界面
//Map<String, Object> map : 当我们向 map 中添加数据时,会默认存放到 request
@RequestMapping(value = "/addMonsterUI")
public String addMonsterUI(Map<String, Object> map) {
/*
* 如果你跳转的页面使用了 SpringMVC 的标签,就需要准备一个对象并且放入到 request 域中,
* 这个对象的属性名 monster 对应 SpringMVC 表单标签的 modelAttribute = "monster"
* */
map.put("monster", new Monster());
return "datavalid/monster_addUI";
}
/**
* 编写方法,处理添加妖怪
* (1) SpringMVC 可以将提交的数据按照参数名和对象的属性名匹配
* (2) 直接封装到对象中
* (3) @Valid Monster monster: 表示需要对 monster 接收的数据进行校验
* (4) Errors errors 表示如果校验出现错误,将校验的错误信息保存到 errors
* (5) Map<String, Object> map 表示如果校验出现错误,将校验的错误信息保存到 map,同时保存 monster 对象
* (6) 校验发生的时机:在 SpringMVC 底层,反射调用目标方法时,会接收到 http 请求的数据,然后根据注解来进行验证,在验证过程中,如果出现了错误,就把错误信息填充到 errors 和 map
*
* */
@RequestMapping(value = "/save")
public String save(@Valid Monster monster, Errors errors, Map<String, Object> map) {
System.out.println("-----monster-----" + monster);
//我们为了看到验证的情况,我们输出 map 和 errors
System.out.println("===== map ======");
for (Map.Entry<String, Object> entry : map.entrySet()) {
System.out.println("key = " + entry.getKey() + " value = " + entry.getValue());
}
System.out.println("===== errors =====");
if (errors.hasErrors()) { //判断是否有错误
List<ObjectError> allErrors = errors.getAllErrors();
for (ObjectError error : allErrors) {
System.out.println("error = " + error);
}
return "datavalid/monster_addUI";
}
return "datavalid/success";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
monster_addUI.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>添加妖怪</title>
</head>
<body>
<h3>添加妖怪</h3>
<%--
这里的表单,我们使用 SpringMVC 的标签来完成
(1) SpringMVC 表单标签在显示之前必须在 request 中有一个 bean,该 bean 的属性和表单标签的字段要对应
(2) request 中的 key 为:form 标签的 modelAttribute 属性值,比如这里的 monsters
(3) SpringMVC 的 form:form 标签的 action 属性值中的 / 不代表 WEB 应用的根目录
(4) 这里使用 SpringMVC 的标签的主要目的是方便提示信息回显
--%>
<form:form action="save" method="post" modelAttribute="monster">
妖怪名字: <form:input path="name"/> <br><br>
妖怪年龄: <form:input path="age"/> <br><br>
电子邮件: <form:input path="email"/> <br><br>
妖怪生日: <form:input path="birthday"/> 要求以"9999-11-11"的形式 <br><br>
薪水: <form:input path="salary"/> 要求以"123,890.12"的形式 <br><br>
<input type="submit" value="添加妖怪"/>
</form:form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>添加成功页面</title>
</head>
<body>
<h1>恭喜,添加成功</h1>
</body>
</html>
2
3
4
5
6
7
8
9
当验证错误时会输出以下语句:
下面进行自定义验证语句
在 springDispatcherServlet-servlet.xml 中配置国际化错误信息的资源处理
<!--配置国际化错误信息的资源处理 bean-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<!--配置国际化文件名字
如果这样配的话,表示 messageSource 会到 src/i18nXXX.properties 去读取错误信息
-->
<property name="basename" value="i18n"></property>
</bean>
2
3
4
5
6
7
配置 i18n.properties 配置文件
NotEmpty.monster.name=\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a //用户名不能为空
typeMismatch.monster.age=\u5e74\u9f84\u4e0d\u80fd\u4e3a\u7a7a //年龄不能为空
typeMismatch.monster.birthday=\u751f\u65e5\u683c\u5f0f\u4e0d\u6b63\u786e //生日格式不正确
typeMismatch.monster.salary=\u85aa\u6c34\u683c\u5f0f\u4e0d\u6b63\u786e //薪水格式不正确
2
3
4
Monster.java
public class Monster {
private Integer id;
private String email;
@Range(min = 1, max = 100) //表示接收的 age 值在 1 - 100 之间
private Integer age;
@NotEmpty //表示 name 不能为空
private String name;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@NumberFormat(pattern = "###,###.##")
private Float salary;
public Monster() {
}
public Monster(Integer id, String email, Integer age, String name, Date birthday, Float salary) {
this.id = id;
this.email = email;
this.age = age;
this.name = name;
this.birthday = birthday;
this.salary = salary;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Float getSalary() {
return salary;
}
public void setSalary(Float salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Monster{" +
"id=" + id +
", email='" + email + '\'' +
", age=" + age +
", name='" + name + '\'' +
", birthday=" + birthday +
", salary=" + salary +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
data_valid.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>SpringMVC[数据格式/验证等]</title>
</head>
<body>
<h1>SpringMVC[数据格式/验证等]</h1>
<hr>
<a href="<%=request.getContextPath()%>/addMonsterUI">添加妖怪</a>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
MonsterHandler
/**
* @Author: 止束
* @Version: 1.0
* @DateTime: 2024/6/26 19:01
* @Description: 处理器用来处理响应用户提交的数据
*/
@Controller
@Scope(value = "prototype") //表示每次请求 MonsterHandler 会生成一个新的对象
public class MonsterHandler {
//显示添加 monster 的界面
//Map<String, Object> map : 当我们向 map 中添加数据时,会默认存放到 request
@RequestMapping(value = "/addMonsterUI")
public String addMonsterUI(Map<String, Object> map) {
/*
* 如果你跳转的页面使用了 SpringMVC 的标签,就需要准备一个对象并且放入到 request 域中,
* 这个对象的属性名 monster 对应 SpringMVC 表单标签的 modelAttribute = "monster"
* */
map.put("monster", new Monster());
return "datavalid/monster_addUI";
}
/**
* 编写方法,处理添加妖怪
* (1) SpringMVC 可以将提交的数据按照参数名和对象的属性名匹配
* (2) 直接封装到对象中
* (3) @Valid Monster monster: 表示需要对 monster 接收的数据进行校验
* (4) Errors errors 表示如果校验出现错误,将校验的错误信息保存到 errors
* (5) Map<String, Object> map 表示如果校验出现错误,将校验的错误信息保存到 map,同时保存 monster 对象
* (6) 校验发生的时机:在 SpringMVC 底层,反射调用目标方法时,会接收到 http 请求的数据,然后根据注解来进行验证,在验证过程中,如果出现了错误,就把错误信息填充到 errors 和 map
*
* */
@RequestMapping(value = "/save")
public String save(@Valid Monster monster, Errors errors, Map<String, Object> map) {
System.out.println("-----monster-----" + monster);
//我们为了看到验证的情况,我们输出 map 和 errors
System.out.println("===== map ======");
for (Map.Entry<String, Object> entry : map.entrySet()) {
System.out.println("key = " + entry.getKey() + " value = " + entry.getValue());
}
System.out.println("===== errors =====");
if (errors.hasErrors()) { //判断是否有错误
List<ObjectError> allErrors = errors.getAllErrors();
for (ObjectError error : allErrors) {
System.out.println("error = " + error);
}
return "datavalid/monster_addUI";
}
return "datavalid/success";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
修改 monster_addUI.jsp
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>添加妖怪</title>
</head>
<body>
<h3>添加妖怪</h3>
<%--
这里的表单,我们使用 SpringMVC 的标签来完成
(1) SpringMVC 表单标签在显示之前必须在 request 中有一个 bean,该 bean 的属性和表单标签的字段要对应
(2) request 中的 key 为:form 标签的 modelAttribute 属性值,比如这里的 monsters
(3) SpringMVC 的 form:form 标签的 action 属性值中的 / 不代表 WEB 应用的根目录
(4) 这里使用 SpringMVC 的标签的主要目的是方便提示信息回显
--%>
<form:form action="save" method="post" modelAttribute="monster">
妖怪名字: <form:input path="name"/> <form:errors path="name"/> <br><br>
妖怪年龄: <form:input path="age"/> <form:errors path="age"/> <br><br>
电子邮件: <form:input path="email"/> <form:errors path="email"/> <br><br>
妖怪生日: <form:input path="birthday"/> <form:errors path="birthday"/> 要求以"9999-11-11"的形式 <br><br>
薪水: <form:input path="salary"/> <form:errors path="salary"/> 要求以"123,890.12"的形式 <br><br>
<input type="submit" value="添加妖怪"/>
</form:form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>添加成功页面</title>
</head>
<body>
<h1>恭喜,添加成功</h1>
</body>
</html>
2
3
4
5
6
7
8
9
# 10.3 细节说明和注意事项
(1)在需要验证的 JavaBean/Pojo 的字段上加上相应的验证注解
(2)目标方法上,在 JavaBean/Pojo 类型的参数前添加 @Valid 注解,告知 SpringMVC 该 bean 是需要验证的
(3)在 @Valid 注解之后,添加一个 Errors 或 BindingResult 类型的参数,可以获取到验证的错误信息
(4)需要使用 <form:errors path="email"></form:errors>
标签来显示错误信息,这个标签,需要写在 <form:form>
标签内生效
(5)错误消息的国际化文件 i18n.properties,中文需要是 Unicode 编码,使用工具进行转码
(6)格式:验证规则.表单 modelAttribute 值.属性名 = 消息信息
NotEmpty.monster.name=\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a //用户名不能为空
typeMismatch.monster.age=\u5e74\u9f84\u4e0d\u80fd\u4e3a\u7a7a //年龄不能为空
2
(7)注解 @NotNull 和 @NotEmpty 的区别说明
1)查看源码可以知道:@NotEmpty:Asserts that the annotated string,collection,map or array is not {@code null} or empty
2)查看源码可以知道:@NotNull:The annotated element must not be {@code null}.* Accepts any type.
3)如果是字符串验证空,建议使用 @NotEmpty
(8)SpringMVC 验证时,会根据不同的验证错误,返回对应的信息
# 10.4 注解的结合使用
# 10.4.1 先看一个问题
在上一个案例中 age 没有填写,是空的,但是却可以提交
# 10.4.2 解决问题
使用 @NotNull + @Range 组合使用解决
private Integer id;
//email 是 String 类型的,使用 @NotEmpty 更合理
private String email;
@NotNull(message = "age不能为空")
@Range(min = 1, max = 100) //表示接收的 age 值在 1 - 100 之间
private Integer age;
@NotEmpty //表示 name 不能为空
private String name;
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
@NumberFormat(pattern = "###,###.##")
private Float salary;
2
3
4
5
6
7
8
9
10
11
12
13
14
# 10.5 数据类型转换校验核心类 - DataBinder
图例 SpringMVC 通过反射机制对目标方法进行解析,将请求消息绑定到处理方法的入参中,数据绑定的核心部件是 DataBinder,运行机制如下:
# 10.6 取消某个属性的绑定
# 10.6.1 使用实例
(1)在默认情况下,表单提交的数据都会和 POJO 类型的 JavaBean 属性绑定,如果程序员在开发中希望取消某个属性的绑定,也就是说,不希望接收到某个表单对应的属性的值,则可以通过 @InitBinder 注解取消绑定
(2)编写一个方法,使用 @InitBinder 标识该方法,可以对 WebDataBinder 对象进行初始化,WebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定
(3)@InitBinder 方法不能有返回值,它必须声明为 void
(4)@InitBinder 方法的参数通常是 WebDataBinder
MonsterHandler.java 中添加方法
/**
* 取消绑定把 monster 的 name 表单提交的值给 monster.name 属性
* 方法上需要标注 @InitBinder,SpringMVC 底层会初始化 WebDataBinder
* 调用 webDataBinder.setDisallowedFields("name"); 表示取消指定属性的绑定
* 即当表单提交的字段名为 name 的,就不再把接收到的字段名为 name 的值填充到 model 数据的 monster 的 name 属性中
* webDataBinder.setDisallowedFields("name") 支持可变参数,可以填写多个字段
* 如果我们取消某个属性的绑定,验证机制就没有意义了,应当把验证的注解去掉
* */
@InitBinder
public void initBinder(WebDataBinder webDataBinder) {
webDataBinder.setDisallowedFields("name");
}
2
3
4
5
6
7
8
9
10
11
12
Monster.java 中把 name 属性的验证给去掉
//@NotEmpty //表示 name 不能为空
private String name;
2
# 10.6.2 注意事项和细节说明
(1)setDisallowedFields() 是可变形参,可以指定多个字段
(2)当将一个字段/属性设置为 disallowed,就不再接收表单提交的值,那么这个字段/属性的值,就是该对象默认的值
(3)一般来说,如果不接收表单字段提交的数据,则该对象字段的验证也就没有意义,所以可以注销掉
# 第 11 章 中文乱码处理
# 11.1 自定义中文乱码过滤器
当表单提交数据为中文时,会出现乱码,来解决一下
public class MyCharacterFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//这里加入对编码的处理
servletRequest.setCharacterEncoding("utf-8");
//放行请求,这个规则和前面讲过的 JavaWeb 的过滤器一样
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--配置处理中文乱码的过滤器,拦截所有请求,处理编码问题-->
<filter>
<filter-name>MyCharacterFilter</filter-name>
<filter-class>com.hspedu.web.filter.MyCharacterFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MyCharacterFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2
3
4
5
6
7
8
9
# 11.2 使用 Spring 提供的过滤器处理中文乱码
<!--配置 Spring 提供的过滤器,解决中文乱码问题-->
<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>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2
3
4
5
6
7
8
9
10
11
12
13
# 第 12 章 处理 json 和 HttpMessageConverter<T>
# 12.1 处理 JSON - @ResponseBody
项目开发中,我们往往需要服务器返回的数据格式是按照 json 来返回的,我们看一下 SpringMVC 是如何处理的
引入处理 json 需要的 jar 包,注意 spring5.x 需要使用 jackson-2.9.x.jar 的包
Dog.java
public class Dog {
private String name;
private String address;
public Dog() {
}
public Dog(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
json.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>json 提交</title>
<%--引入 JQuery--%>
<script type="text/javascript" src="script/jquery-3.6.0.min.js"></script>
<%--编写 JQuery 代码和请求--%>
<script type="text/javascript">
$(function () {
//给 id="getJson" 绑定点击事件
$("#getJson").click(function () {
//console.log("ok ...");
var url = this.href;
var args = {"time" : new Date()}; //这是要发送的数据,为了防止页面缓存
$.post(
url,
args,
function (data) {
console.log("data = ", data);
console.log("dog.name = ", data.name);
console.log("dog.address = ", data.address);
},
"json"
)
return false;
})
})
</script>
</head>
<body>
<h1>请求一个 json 数据</h1>
<%--当用户点击超链接时,我们发出一个 Ajax 请求--%>
<a href="<%=request.getContextPath()%>/json/dog" id="getJson">点击获取 json 数据</a>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
JsonHandler.java
@Controller
public class JsonHandler {
/*
* 目标方法有 @ResponseBode 注解,表示返回的数据是 json 格式
* SpringMVC 底层根据目标方法 @ResponseBody,返回指定格式,根据 http 请求来进行处理
* */
@RequestMapping(value = "/json/dog")
@ResponseBody
public Dog getJson() {
//SpringMVC 会根据设置,转成 json 格式数据返回
Dog dog = new Dog();
dog.setName("大黄狗");
dog.setAddress("小新的家");
return dog;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 12.2 处理 JSON - @RequestBody
前面是通过表单,或者 url 请求携带 参数名=参数值 把数据提交给目标方法
(1)这里举例客户端发送 json 字符串数据
(2)使用 SpringMVC 的 @RequestBody 将客户端提交的 json 数据封装成 JavaBean 对象
(3)再把这个 JavaBean 以 json 对象的形式返回
User.java
public class User {
private String userName;
private Integer age;
public User() {
}
public User(String userName, Integer age) {
this.userName = userName;
this.age = age;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
json.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>json 提交</title>
<%--引入 JQuery--%>
<script type="text/javascript" src="script/jquery-3.6.0.min.js"></script>
<%--编写 JQuery 代码和请求--%>
<script type="text/javascript">
$(function () {
//给 id="getJson" 绑定点击事件
$("#getJson").click(function () {
//console.log("ok ...");
var url = this.href;
var args = {"time" : new Date()}; //这是要发送的数据,为了防止页面缓存
$.post(
url,
args,
function (data) {
console.log("data = ", data);
console.log("dog.name = ", data.name);
console.log("dog.address = ", data.address);
},
"json"
)
return false;
})
//绑定按钮点击事件,提交 json 数据
//SpringMVC 可以在后台将 json 转成对象
$("button[name = 'butt1']").click(function () {
//将 userName 和 age 封装成 json 字符串,发送给目标方法
var url = "/springmvc/save2";
var userName = $("#userName").val();
var age = $("#age").val();
//将 json 对象转成 json 字符串
var args = JSON.stringify({"userName": userName, "age": age});
$.ajax({
url: url,
data: args,
type: "POST",
success: function (data) {
console.log("返回的 data = ", data);
},
//下面这个 contentType 参数,是指定发送数据时的编码和格式
contentType: "application/json;charset=utf-8"
})
})
})
</script>
</head>
<body>
<h1>请求一个 json 数据</h1>
<%--当用户点击超链接时,我们发出一个 Ajax 请求--%>
<a href="<%=request.getContextPath()%>/json/dog" id="getJson">点击获取 json 数据</a>
<h1>发送一个 json 数据</h1>
u:<input id="userName" type="text"> <br/>
a:<input id="age" type="text"> <br/>
<button name="butt1">添加用户</button>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
JsonHandler.java
/**
* @RequestBody User user 在形参指定了 @RequestBody
* SpringMVC 就会将提交的 json 字符串数据填充给指定的 JavaBean
*/
@RequestMapping(value = "/save2")
@ResponseBody
public User save2(@RequestBody User user) {
//将前台传过来的数据以 json 的格式响应回浏览器
System.out.println("user = " + user);
return user;
}
2
3
4
5
6
7
8
9
10
11
# 12.3 处理 JSON - 注意事项和细节
(1)目标方法正常返回 JSON 需要的数据,可以是一个对象,也可以是一个集合
//编写方法,以 json 格式返回多个 Dog
@RequestMapping(value = "/json/dogs")
@ResponseBody
public List<Dog> getJsons() {
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog("大黄狗", "小新的家"));
dogs.add(new Dog("大黄狗2", "小新2的家"));
dogs.add(new Dog("大黄狗3", "小新3的家"));
return dogs;
}
2
3
4
5
6
7
8
9
10
(2)@ResponseBody 可以直接写在 Handler 类上,这样就对这个类的所有方法生效
(3)@ResponseBody + @Controller 可以直接写成 @RestController
# 12.4 HttpMessageConverter<T>
SpringMVC 处理 JSON 底层实现是依靠 HttpMessageConverter<T> 来进行转换的
处理 JSON 的底层实现(HttpMessageConverter<T>
):
(1)使用 HttpMessageConverter<T>
将请求信息转化并绑定到处理方法的入参中,或将响应结果转为对应类型的响应信息,Spring 提供了两种途径:
1)使用 @RequestBody / @ResponseBody 对目标方法进行标注
2)使用 HttpEntity<T>
/ ResponseEntity<T>
作为目标方法的入参或返回值
(2)当控制器处理方法使用到 @RequestBody / @ResponseBody 或 HttpEntity<T>
/ ResponseEntity<T>
时,Spring 首先根据请求头或响应头的 Accept 属性选择匹配的 HttpMessageConverter,进而根据参数类型或泛型类型的过滤得到匹配的 HttpMessageConverter,若找不到可用的 HttpMessageConverter 将报错
# 12.5 文件下载 ResponseEntity<T>
在 SpringMVC 中,通过返回 ResponseEntity<T>
的类型,可以实现文件下载的功能
json.jsp
<h1>下载文件的测试</h1>
<a href="<%=request.getContextPath()%>/downFile">点击下载文件</a>
2
JsonHandler.java
//响应用户下载文件的请求
@RequestMapping(value = "/downFile")
public ResponseEntity<byte[]> downFile(HttpSession session) throws Exception {
//(1) 先获取到下载文件的 inputStream
InputStream resourceAsStream = session.getServletContext().getResourceAsStream("/img/1.jpg");
//(2) 开辟一个存放文件的 byte 数组,这里使用 byte[] 是可以支持二进制数据
byte[] bytes = new byte[resourceAsStream.available()];
//(3) 将下载的文件的数据读入到 byte 数组
resourceAsStream.read(bytes);
//(4) 创建返回的 HttpStatus
HttpStatus httpStatus = HttpStatus.OK;
//(5) 创建 headers
HttpHeaders headers = new HttpHeaders();
//指定返回的数据,客户端应当以附件的形式下载
headers.add("Content-Disposition", "attachment;filename=1.jpg");
//(6) 构建一个 ResponseEntity 对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, httpStatus);
return responseEntity;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 12.6 作业布置
把前面的数据格式化、验证以及国际化、JSON处理,文件下载相关代码和案例写一遍
# 第 13 章 SpringMVC 文件上传
# 13.1 基本介绍
(1)SpringMVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的,Spring 用 Jakarta Commons FileUpload 技术实现了一个 MultipartResolver 实现类:CommonsMultipartResovler
(2)SpringMVC 上下文中默认没有装配 MultipartResolver,因此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需先在上下文中配置 MultipartResolver
# 13.2 代码实现
(1)引入 SpringMVC 文件上传需要的 jar 包
(2)配置文件上传解析器,在 springDispatcherServlet-servlet.xml
fileUpload.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>文件上传</title>
</head>
<body>
<h1>文件上传的演示</h1>
<form action="<%=request.getContextPath()%>/fileUpload" method="post" enctype="multipart/form-data">
文件介绍: <input type="text" name="introduce"> <br>
选择文件: <input type="file" name="file"> <br/>
<input type="submit" value="上传文件">
</form>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
springDispatcherServlet-servlet.xml
<!--配置文件上传需要的 bean-->
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver"/>
2
FileUploadHandler.java
@Controller
public class FileUploadHandler {
//编写方法,处理文件上传的请求
@RequestMapping(value = "/fileUpload")
public String fileUpload(@RequestParam(value = "file") MultipartFile file, HttpServletRequest request, String introduce) throws IOException {
//接收到提交的文件名
String originalFilename = file.getOriginalFilename();
System.out.println("你上传的文件名 = " + originalFilename);
System.out.println("introduce = " + introduce);
//得到要把上传文件保存到哪个路径
String fileFullPath = request.getServletContext().getRealPath("/img/" + originalFilename);
//创建文件
File saveToFile = new File(fileFullPath);
//将上传的文件转存到 saveToFile
file.transferTo(saveToFile);
return "success";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 第 14 章 自定义拦截器
# 14.1 什么是拦截器
(1)SpringMVC 也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能
(2)自定义的拦截器必须实现 HandlerInterceptor 接口
(3)自定义拦截器的三个方法:
1)preHandler():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理
2)postHandler():这个方法在目标方法处理完请求后执行
3)afterCompletion():这个方法在完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作
# 14.2 自定义拦截器执行流程分析图
(1)如果 preHandle 方法返回 false,则不再执行目标方法,可以在此指定返回页面
(2)postHandle 在目标方法被执行后执行,可以在方法中访问到目标方法返回的 ModelAndView 对象
(3)若 preHandle 返回 true,则 afterCompletion 方法在渲染视图之后被执行
(4)若 preHandle 返回 false,则 afterCompletion 方法不会被调用
(5)在配置拦截器时,可以指定该拦截器对哪些请求生效,哪些请求不生效
# 14.3 自定义拦截器应用实例
# 14.3.1 快速入门
完成一个自定义拦截器,学习如何配置拦截器和拦截器的运行流程
过滤器由 Tomcat 接管,所以在 web.xml 中配置,而拦截器是由 SpringMVC 接管,所以在 springDispatcherServlet-servlet.xml 配置
interceptor.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>测试自定义拦截器</title>
</head>
<body>
<h1>测试自定义拦截器</h1>
<a href="<%=request.getContextPath()%>/hi">测试自定义拦截器 - hi()</a>
<a href="<%=request.getContextPath()%>/hello">测试自定义拦截器 - hello()</a>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
拦截器 MyInterceptor01.java
@Component
public class MyInterceptor01 implements HandlerInterceptor {
/**
* @Author: 止束
* @Params: [request, response, handler]
* @Return boolean
* @Description:
* preHandle() 在目标方法执行前被执行
* 如果 preHandle() 返回 false,就不再执行目标方法
* 该方法可以获取到 request、response、handler
* 这里可以根据业务,进行拦截,并指定跳转到哪个页面
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("--MyInterceptor01--preHandle()--");
return true;
}
/**
* @Author: 止束
* @Params: [request, response, handler, modelAndView]
* @Return void
* @Description:
* postHandle() 在目标方法执行后执行
* 该方法可以获取到目标方法返回的 ModelAndView 对象
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("--MyInterceptor01--postHandle()--");
}
/**
* @Author: 止束
* @Params: [request, response, handler, ex]
* @Return void
* @Description:
* afterCompletion() 在视图渲染后被执行,这里可以进行资源清理工作
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("--MyInterceptor01--afterCompletion()--");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
目标方法 FurnHandler.java
@Controller
public class FurnHandler {
@RequestMapping(value = "/hi")
public String hi() {
System.out.println("--FurnHandler--hi()--");
return "success";
}
@RequestMapping(value = "/hello")
public String hello() {
System.out.println("--FurnHandler--hello()--");
return "success";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
配置拦截器 springDispatcherServlet-servlet.xml
<!--配置自定义拦截器-->
<mvc:interceptors>
<!--
第一种配置方式:直接使用 ref 引用到对应的 myInterceptor01,这种方式会拦截所有的目标方法
-->
<ref bean="myInterceptor01"/>
</mvc:interceptors>
2
3
4
5
6
7
# 14.3.2 注意事项和细节
(1)默认的配置会对所有的目标方法进行拦截,也可以指定哪些方法需要拦截,比如只拦截 hi()
<!--配置自定义拦截器-->
<mvc:interceptors>
<!--
第二种配置方式:
<mvc:mapping path="/hi"/> 指定要拦截哪个路径的请求,即要拦截哪个方法
<ref bean="myInterceptor01"/> 指定使用哪个拦截器去拦截
-->
<mvc:interceptor>
<mvc:mapping path="/hi"/>
<ref bean="myInterceptor01"/>
</mvc:interceptor>
</mvc:interceptors>
2
3
4
5
6
7
8
9
10
11
12
(2)mvc:mapping 支持通配符,同时指定不对哪些目标方法进行拦截
<!--配置自定义拦截器-->
<mvc:interceptors>
<!--
第三种配置方式:
<mvc:mapping path="/h*"/> 通配符方式,表示拦截以 /h 打头的路径
<mvc:exclude-mapping path="/hello"/> 表示不拦截 /hello
<ref bean="myInterceptor01"/> 指定使用哪个拦截器去拦截
-->
<mvc:interceptor>
<mvc:mapping path="/h*"/>
<mvc:exclude-mapping path="/hello"/>
<ref bean="myInterceptor01"/>
</mvc:interceptor>
</mvc:interceptors>
2
3
4
5
6
7
8
9
10
11
12
13
14
(3)拦截器需要配置才生效,不配置是不生效的
(4)如果 preHandler() 方法返回了 false,就不会执行目标方法,那么就可以在这个方法里根据业务指定需要跳转的页面
# 14.3.3 Debug 执行流程
# 14.4 多个拦截器
# 14.4.1 多个拦截器执行流程示意图
<!--配置自定义拦截器-->
<mvc:interceptors>
<!--
第三种配置方式:
<mvc:mapping path="/h*"/> 通配符方式,表示拦截以 /h 打头的路径
<mvc:exclude-mapping path="/hello"/> 表示不拦截 /hello
<ref bean="myInterceptor01"/> 指定使用哪个拦截器去拦截
-->
<mvc:interceptor>
<mvc:mapping path="/h*"/>
<mvc:exclude-mapping path="/hello"/>
<ref bean="myInterceptor01"/>
</mvc:interceptor>
<!--配置第二个拦截器-->
<mvc:interceptor>
<mvc:mapping path="/h*"/>
<ref bean="myInterceptor02"/>
</mvc:interceptor>
</mvc:interceptors>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class MyInterceptor02 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("--MyInterceptor02--preHandle--");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("--MyInterceptor02--postHandle--");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("--MyInterceptor02--afterCompletion");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 14.4.2 应用实例 1
如果用户提交的数据有禁用词(比如:病毒),则在第一个拦截器就返回,不执行目标方法
@Component
public class MyInterceptor01 implements HandlerInterceptor {
/**
* @Author: 止束
* @Params: [request, response, handler]
* @Return boolean
* @Description:
* preHandle() 在目标方法执行前被执行
* 如果 preHandle() 返回 false,就不再执行目标方法
* 该方法可以获取到 request、response、handler
* 这里可以根据业务,进行拦截,并指定跳转到哪个页面
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("--MyInterceptor01--preHandle()--");
//获取到用户提交的关键字
String keyword = request.getParameter("keyword");
if ("病毒".equals(keyword)) {
//请求转发到 warning
request.getRequestDispatcher("/WEB-INF/pages/warning.jsp").forward(request, response);
return false;
}
System.out.println("得到的 keyword = " + keyword);
return true;
}
/**
* @Author: 止束
* @Params: [request, response, handler, modelAndView]
* @Return void
* @Description:
* postHandle() 在目标方法执行后执行
* 该方法可以获取到目标方法返回的 ModelAndView 对象
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("--MyInterceptor01--postHandle()--");
}
/**
* @Author: 止束
* @Params: [request, response, handler, ex]
* @Return void
* @Description:
* afterCompletion() 在视图渲染后被执行,这里可以进行资源清理工作
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("--MyInterceptor01--afterCompletion()--");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>警告</title>
</head>
<body>
<h1>不要乱讲话</h1>
</body>
</html>
2
3
4
5
6
7
8
9
# 14.5 作业布置
(1)把前面的 SpringMVC 文件上传,自定义拦截器相关代码和案例过一遍
(2)简述 SpringMVC 自定义拦截器工作流程并画出示意图
# 第 16 章 异常处理
# 16.1 异常处理基本介绍
(1)SpringMVC 通过 HandlerExceptionResolver 处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常
(2)主要处理 Handler 中用 @ExceptionHandler 注解定义的方法
(3)ExceptionHandlerMethodResolver 内部若找不到 @ExceptionHandler 注解的话,会找 @ControllerAdvice 类的 @ExceptionHandler 注解方法,这样就相当于一个全局异常处理器
# 16.2 局部异常
如果不处理异常,界面非常不友好
exception.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>异常信息</title>
</head>
<body>
<h1>测试异常</h1>
<a href="<%=request.getContextPath()%>/testException01?num=0">点击测试异常</a> <br><br>
</body>
</html>
2
3
4
5
6
7
8
9
10
MyExceptionHandler.java
@Controller
public class MyExceptionHandler {
//编写方法,模拟异常,如果我们不做异常处理,是由 Tomcat 默认页面显示
@RequestMapping(value = "/testException01")
public String test01(Integer num) {
int i = 9 / num;
return "success";
}
/**
* @Author: 止束
* @Params: []
* @Return java.lang.String
* @Description:
* localException() 方法可用来处理局部异常,即本类发生的异常
* @ExceptionHandler({ArithmeticException.class, NullPointerException.class}) 表示有算术异常和空指针异常就来到 localException() 这个方法处理
* Exception ex 表示生成的异常对象,可以通过 ex 得到相关的信息
*/
@ExceptionHandler({ArithmeticException.class, NullPointerException.class})
public String localException(Exception ex, HttpServletRequest request) {
System.out.println("局部异常信息是 = " + ex.getMessage());
//将异常信息带到下一个页面
request.setAttribute("reason", ex.getMessage());
return "exception_mes";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
exception_mes.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>异常信息提示</title>
</head>
<body>
<h1>程序发生了异常</h1>
异常信息 - ${requestScope.reason}
</body>
</html>
2
3
4
5
6
7
8
9
10
# 16.3 全局异常
全局异常处理机制:ExceptionHandlerMethodResolver 内部若找不到 @ExceptionHandler 注解的话,会找 @ControllerAdvice 类的 @ExceptionHandler 注解方法,这样就相当于一个全局异常处理器
exception.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>异常信息</title>
</head>
<body>
<h1>测试异常</h1>
<a href="<%=request.getContextPath()%>/testException01?num=0">点击测试局部异常</a> <br><br>
<a href="<%=request.getContextPath()%>/testGlobalException">点击测试全局异常</a> <br><br>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
MyExceptionHandler.java 里有一个模拟异常的方法
@Controller
public class MyExceptionHandler {
//编写方法,模拟异常,如果我们不做异常处理,是由 Tomcat 默认页面显示
@RequestMapping(value = "/testException01")
public String test01(Integer num) {
int i = 9 / num;
return "success";
}
/**
* @Author: 止束
* @Params: []
* @Return java.lang.String
* @Description:
* localException() 方法可用来处理局部异常,即本类发生的异常
* @ExceptionHandler({ArithmeticException.class, NullPointerException.class}) 表示有算术异常和空指针异常就来到 localException() 这个方法处理
* Exception ex 表示生成的异常对象,可以通过 ex 得到相关的信息
*/
@ExceptionHandler({ArithmeticException.class, NullPointerException.class})
public String localException(Exception ex, HttpServletRequest request) {
System.out.println("局部异常信息是 = " + ex.getMessage());
//将异常信息带到下一个页面
request.setAttribute("reason", ex.getMessage());
return "exception_mes";
}
//测试全局异常处理
@RequestMapping(value = "/testGlobalException")
public String global() {
//这里模拟一个数字转换异常 NumberFormatException
//该异常没有在局部异常中处理,按照异常处理机制,就会交给全局异常处理类处理
int num = Integer.parseInt("hello");
return "success";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
MyGlobalException.java 全局异常的处理
/**
* @Author: 止束
* @Version: 1.0
* @DateTime: 2024/7/3 17:21
* @Description:
* 如果类上标注了 @ControllerAdvice,就是一个全局异常处理类
*/
@ControllerAdvice
public class MyGlobalException {
/*
* 全局异常就是不管是哪个 Handler 抛出的异常,都可以被捕获
* 这里处理的全局异常就是 NumberFormatException.class, ClassCastException.class
* */
@ExceptionHandler({NumberFormatException.class, ClassCastException.class})
public String globalException(Exception ex, HttpServletRequest request) {
System.out.println("全局异常处理 - " + ex.getMessage());
//将异常信息带到下一个页面
request.setAttribute("reason", ex.getMessage());
return "exception_mes";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
exception_mes.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>异常信息提示</title>
</head>
<body>
<h1>程序发生了异常</h1>
异常信息 - ${requestScope.reason}
</body>
</html>
2
3
4
5
6
7
8
9
10
注意:异常处理时,局部异常优先级高于全局异常
# 16.4 自定义异常
(1)通过 @ResponseStatus 注解,可以自定义异常
自定义异常 AgeException.java
@ResponseStatus(reason = "年龄需要在 1-120 之间", value = HttpStatus.BAD_REQUEST)
public class AgeException extends RuntimeException {
}
2
3
4
exception.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>异常信息</title>
</head>
<body>
<h1>测试异常</h1>
<a href="<%=request.getContextPath()%>/testException01?num=0">点击测试局部异常</a> <br><br>
<a href="<%=request.getContextPath()%>/testGlobalException">点击测试全局异常</a> <br><br>
<a href="<%=request.getContextPath()%>/testException02">点击测试自定义异常</a> <br><br>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
模拟异常
@RequestMapping(value = "/testException02")
public String test02() {
throw new AgeException();
}
2
3
4
# 16.5 SimpleMappingExceptionResolver
# 16.5.1 基本说明
(1)如果希望对所有异常进行统一处理,可以使用 SimpleMappingExceptionResolver
(2)它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常
(3)需要在 IOC 容器中配置
# 16.5.2 应用实例
对数组越界异常进行统一处理,使用 SimpleMappingExceptionResolver
exception.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>异常信息</title>
</head>
<body>
<h1>测试异常</h1>
<a href="<%=request.getContextPath()%>/testException01?num=0">点击测试局部异常</a> <br><br>
<a href="<%=request.getContextPath()%>/testGlobalException">点击测试全局异常</a> <br><br>
<a href="<%=request.getContextPath()%>/testException02">点击测试自定义异常</a> <br><br>
<a href="<%=request.getContextPath()%>/testException03">点击测试统一处理异常</a> <br><br>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
MyExceptionHandler.java 里有个模拟数组越界异常的方法
@RequestMapping(value = "/testException03")
public String test03() {
int[] arr = new int[]{3, 9, 10, 190};
//抛出一个数组越界的异常 ArrayIndexOutOfBoundsException
System.out.println(arr[90]);
return "success";
}
2
3
4
5
6
7
springDispatcherServlet-servlet.xml
<!--配置统一处理异常的 Bean
arrEx 是处理显示的页面,比如 arrEx.jsp
-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">arrEx</prop>
</props>
</property>
</bean>
2
3
4
5
6
7
8
9
10
arrEx.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>数组越界异常</title>
</head>
<body>
异常信息: 数组越界异常
</body>
</html>
2
3
4
5
6
7
8
9
# 16.5.3 对未知异常进行统一处理
对未知异常进行统一处理,使用 SimpleMappingExceptionResolver
exception.jsp
<a href="<%=request.getContextPath()%>/testException04">点击测试未知异常</a> <br><br>
2
模拟异常
//模拟发生没有归类的异常
@RequestMapping(value = "/testException04")
public String test04() {
String str = "hello";
//这里会抛出 StringIndexOutOfBoundsException 异常
char c = str.charAt(10);
return "success";
}
2
3
4
5
6
7
8
<!--配置统一处理异常的 Bean
arrEx 是处理显示的页面,比如 arrEx.jsp
-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">arrEx</prop>
<prop key="java.lang.Exception">allEx</prop>
</props>
</property>
</bean>
2
3
4
5
6
7
8
9
10
11
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>未知异常信息</title>
</head>
<body>
<h1>系统发生了未知异常</h1>
</body>
</html>
2
3
4
5
6
7
8
9
# 16.5.4 异常处理的优先级
局部异常 > 全局异常 > SimpleMappingExceptionResolver > Tomcat 默认机制