Spring MVC 是 Spring Framework 提供的 Web 组件,是目前主流的实现 MVC 设计模式的框架,提供前端路由映射、视图解析等功能,Java Web 是开发者必须要掌握的技术框架。请注意本文使用 Spring Boot 2.7.6 进行演示,直接使用 spring-boot-starter-web 依赖,同时也默认前后端分离,本文重点在于第5章的【Spring Boot + MyBatis-Plus + JSP】项目实战。
目录
① 通过 IDEA Spring Initializr 创建项目
3 mybatis-plus 编写 Dao层 + Service层
⑦ @Requestbody自动解析 JSON 字符串封装到对象
5 【Spring Boot + MyBatis-Plus + JSP】项目实战
① /user/homePage 接口及 homePage 视图
② /user/allUser 接口及 allUser 视图
③ /user/toAddUser、/user/addUser 接口及 addUser 视图
④ /user/toUpdateUser、/user/updateUser 接口及 updateUser 视图
6.1 ResponseStatusExceptionResolver
①自定义一个异常类,并且使用 @ResponseStatus 注解修饰 👇
6.2 ExceptionHandlerExceptionResolver
③ 定义全局异常处理类 GlobalExceptionHandler
项目源码:尹煜 / SpringMvcJsp · GitCode
1 Spring MVC 介绍
1.1 MVC 是什么
MVC是一种软件架构思想,把软件按照模型--M,视图--V,控制器--C 来划分
Model:模型层,指工程中的 JavaBean,用来处理数据 JavaBean 分成两类:
- 一类称为实体类Bean:专门用来存储业务数据,比如Student,User
- 一类称为业务处理Bean:指Servlet或Dao对象,专门用来处理业务逻辑和数据访问
View:视图层,指工程中的html,jsp等页面,作用是和用户进行交互,展示数据
Controler:控制层,指工程中的Servlet,作用是接收请求和响应浏览器
MVC 简单流程:
- 用户通过视图层发送请求到服务器,在服务器中请求被 Controller 接收
- Controller 调用相应的 Model 层处理请求,处理完毕后结果返回到 Controller
- Controller 再根据请求处理的结果找到对应的 View 视图,渲染数据后最终响应给浏览器
Spring MVC 对这套 MVC 流程进行封装,帮助开发者屏蔽底层细节,并且开放出相关接口供开发者调用,让 MVC 开发更简单方便
该流程及图片转载自 Spring MVC详解(学习总结) ,写的真的很不错!
1.2 Spring MVC 核心组件
- DispatcherServlet:前置控制器,负责调度其他组件的执行,可以降低不同组件之间的耦合性,是整个Spring MVC的核心模块
- Handler:处理器,完成具体的业务逻辑,相当于Servlet
- HandlerMapping:DispatcherServlet是通过 HandlerMapping把请求映射到不同的Handler
- HandlerInterceptor:处理器拦截器,是一个接口,如果我们需要进行一些拦截处理,可以通过实现该接口完成
- HandlerExecutionChain:处理器执行链,包括两部分内容:Handler和HandlerInterceptor(系统会有一个默认的HandlerInterceptor,如果有额外拦截处理,可以添加拦截器进行设置)
- HandlerAdapter:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作包括表单的数据验证、数据类型转换、把表单数据封装到POJO等,这些一系列的操作都是由HandlerAdapter完成,DispatcherServlet通过HandlerAdapter执行不同的Handler
- ModelAndView:封装了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet,项目中会用到!
- ViewResolver:视图解析器,DispatcherServlet通过它把逻辑视图解析为物理视图,最终把渲染的结果响应给客户端
1.3 Spring MVC 工作流程
- 客户端请求被 DispatcherServlet 接收
- 根据 HandlerMapping 映射到 Handler
- 生成 Handler 和 HandlerInterceptor
- Handler 和 HandlerInterceptor 以 HandlerExecutionChain 的形式一并返回给 DispatcherServlet
- DispatcherServlet 通过 HandlerAdapter 调用 Handler 的方法完成业务逻辑处理
- 返回一个 ModelAndView 对象给 DispatcherServlet
- DispatcherServlet 把获取的 ModelAndView 对象传给 ViewResolver 视图解析器,把逻辑视图解析成物理视图
- ViewResolver 返回一个View进行视图渲染(把模型填充到视图中)
- DispatcherServlet 把渲染后的视图响应给客户端
2 搭建项目
2.1 Mysql 数据库初始化
本人在云服务器上部署了 Mysql (部署教程链接:Docker 环境下安装 Mysql),同时已通过 DBeaver 数据库可视化软件,此时我需要通过 DBeaver 在 Mysql 中创建表和插入数据。
① 新建数据库
通过 DBeaver 连接刚部署完的 Mysql 后发现空无一物 👇
此时右键选择新建数据库并命名,选择 utf-8 (兼容性强)编码格式
创建成功 👇
② 数据库 Schema 脚本
简单来说就是创建数据库的 SQL 脚本代码 👇
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
③ 数据库 Data 脚本
添加相应数据的 SQL 脚本代码 👇
DELETE FROM user;
INSERT INTO user (id, name, age, email) VALUES
(1, 'yinyu', 18, 'yinyu@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
④ 使用 SQL 编辑器运行脚本
点击菜单【SQL编辑器】新建 SQL 编辑器,依次执行 Schema 脚本和 Data 脚本
user 数据表创建成功 👇
⑤ id 主键设置自增
通过 DBeaver 设置,Sql 语句和其他数据库可视化软件都可以的
2.2 Spring Boot 项目初始化
① 通过 IDEA Spring Initializr 创建项目
StringBoot 默认不支持使用 jar 包打包有 JSP 页面项目,所以如果包含有 JSP 页面的项目,需要使用 war 打包,由于本文使用 Jsp 项目,因此使用 war 打包。
② 添加 Spring Web(最关键)等依赖
注意本文的 Spring Boot 版本是 2.7.6 ,建议将版本控制在 2-3 之间,超出范围的话会产生兼容问题。
③ 导入 mybatis-plus 依赖
注意本项目用的 mybatis-plus 依赖是 3.5.2 版本,算是比较新的版本
路径:pom.xml
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
④ 连接数据库配置
路径:src/main/resources/application.properties
#数据库连接配置
spring.datasource.username=root
spring.datasource.password=root
#mysql5~8 驱动不同driver-class-name 8需要增加时区的配置serverTimezone=UTC,放在url最后
#useSSL=false 安全连接
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
⑤ 编写数据库对应的实体类
使用 lombok 和 mybatisplus 的实体类注释,加大开发效率
注意:mybatis-plus 会通过实体类的名称自动匹配数据表,比如 User 实体类和 user 数据表,也可通过 @Ttable 指定,如 @Ttable("user"),本文采用第一种形式
路径:src/main/java/com/yinyujsp/pojo/User.java
package com.yinyujsp.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)//新增记录时未命名id时id自增
private Long id;
private String name;
private Integer age;
private String email;
}
3 mybatis-plus 编写 Dao层 + Service层
关于 mybatis-plus 的具体使用可查看【MyBatisPlus-3.5.2】专栏,因为本文目标是 Spring MVC,所以不会对 mybatis-plus 过多深入,本文编写 Mapper 和 IService 的增删改查等简单操作。
3.1 Dao层的 Mapper接口
① 编写实体类对应的mapper接口
路径:src/main/java/com/yinyujsp/mapper/UserMapper.java
package com.yinyujsp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yinyujsp.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
//在对应的接口上面继承一个基本的接口 BaseMapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
//mybatisplus 将所有CRUD操作都编写完成了,不用像以前一样配置一大堆文件
}
② 在主启动类添加@MapperScan注解
路径:src/main/java/com/yinyujsp/YinyujspApplication.java
package com.yinyujsp;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.yinyujsp.mapper")
@SpringBootApplication
public class YinyujspApplication {
public static void main(String[] args) {
SpringApplication.run(YinyujspApplication.class, args);
}
}
3.2 Service层的 Iservice 接口和实现类
① 编写实体类对应的 UserBaseService 接口
路径:src/main/java/com/yinyujsp/service/UserBaseService.java
package com.yinyujsp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yinyujsp.pojo.User;
//如有需要用以重写IService里的抽象方法,如不需要重写也可去掉该文件,将IService<User>写在UserServiceImpl文件
public interface UserBaseService extends IService<User> {
}
② Service 层的实现类
接下来就是在实现类写具体的增删改查操作了~
我将使用 mapper 和 Iservice 接口的方法用 _ByMapper 后缀做了区分,未加后缀的方法调用的是Iservice 接口,以此方便大家理解使用,实际项目中不会区分得这么细,一个方法中这两接口都可能存在。
路径:src/main/java/com/yinyujsp/service/impl/UserServiceImpl.java
package com.yinyujsp.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yinyujsp.mapper.UserMapper;
import com.yinyujsp.pojo.User;
import com.yinyujsp.service.UserBaseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserBaseService {
@Autowired
private UserMapper userMapper;
/*
Iservice CRUD(增删改查)
*/
//增加一个User
public boolean addUser(User user){
return save(user);
}
//根据id删除一个User
public boolean deleteUserById(int id){
return removeById(id);
}
//更新User
public boolean updateUser(User user){
return updateById(user);
}
//根据id查询,返回一个User
public User queryUser(int id){
return getById(id);
}
//查询全部User,返回list集合
public List<User> queryAllUser(){
return list();
}
/*
Mapper CRUD(增删改查)
*/
//增加一个User
public int addUser_ByMapper(User user){
return userMapper.insert(user);
}
//根据id删除一个User
public int deleteUserById_ByMapper(int id){
return userMapper.deleteById(id);
}
//更新User
public boolean updateUser_ByMapper(User user){
userMapper.updateById(user);
return true;
}
//根据id查询,返回一个User
public User queryUser_ByMapper(int id){
return userMapper.selectById(id);
}
//查询全部User,返回list集合
public List<User> queryAllUser_ByMapper(){
return userMapper.selectList(new QueryWrapper<>());//QueryWrapper没有任何条件
}
}
4 定义 Controller
4.1 使用 @RequestMapping
此种方式是最常用的,使用注解相对轻量级一些,至于远古方式就不介绍了~
@RestController 是 @controller(注入容器) 和 @ResponseBody(用于返回数据,若返回实体类,会包装成 Json 格式) 的结合
路径:src/main/java/com/yinyujsp/controller/demoController.java
@RestController
@RequestMapping("/demo")
public class demoController {
@RequestMapping("/RequestMapping")
public String demo() {
return "HelloWord";
}
}
响应成功 👇
① 支持Restful风格
支持 Restful 风格,使用 method 属性定义对资源的操作方式,
@RequestMapping(value = "/restful", method = RequestMethod.GET)
public String get() {
//查询
return "get";
}
@RequestMapping(value = "/restful", method = RequestMethod.POST)
public String post() {
//创建
return "post";
}
@RequestMapping(value = "/restful", method = RequestMethod.PUT)
public String put() {
//更新
return "put";
}
@RequestMapping(value = "/restful", method = RequestMethod.DELETE)
public String del() {
//删除
return "post";
}
② 支持Ant风格
//匹配 /antA 或者 /antB 等URL
@RequestMapping("/ant?")
public String ant() {
return "ant";
}
//匹配 /ant/a/create 或者 /ant/b/create 等URL
@RequestMapping("/ant/*/create")
public String antCreate() {
return "antCreate";
}
//匹配 /ant/create 或者 /ant/a/b/create 等URL
@RequestMapping("/ant/**/create")
public String antAllCreate() {
return "antAllCreate";
}
4.2 接收参数
定义完Controller之后,需要接收前端传入的参数,主要有以下几种形式 ~
① 普通参数
在 @RequestMapping 映射方法上直接写上接收参数名即可:
@RequestMapping(value = "/queryUserById")
public User queryUserById(int id) {
return userService.queryUser(id);
}
请求成功 👇
② @RequestParam参数名绑定
如果不想使用形参名称作为参数名称,可以使用 @RequestParam 进行参数名称绑定:
@RequestMapping(value = "/queryNameById")
public String queryNameById(@RequestParam(value = "userId", required = false, defaultValue = "0") int id) {
return userService.queryUser(id).getName();
}
请求成功 👇
③ @PathVariable路径参数
通过 @PathVariable 将 URL 中的占位符 {xxx} 参数映射到操作方法的入参,演示代码如下:
@RequestMapping(value = "/queryEmailById/{id}")
public String queryEmailById(@PathVariable("id") int id) {
return userService.queryUser(id).getEmail();
}
请求成功 👇
④ @RequestHeader绑定请求头属性
使用@RequestHeader注解,用法和@RequestParam类似:
@RequestMapping("/queryHead")
public String queryHead(@RequestHeader("Accept-Encoding") String acceptEncoding) {
return acceptEncoding;
}
请求成功 👇
查看接口请求头详情,匹配正确~
⑤ @CookieValue绑定请求的Cookie值
获取 Request 请求中 Cookie 的值:
@RequestMapping("/querycookie")
public String querycookie(@CookieValue("JSESSIONID") String jSESSIONID) {
return jSESSIONID;
}
返回 cookie 值 👇
查看接口 cookie 详情,匹配正确~
⑥ 绑定请求参数到实体类对象
@RequestMapping("/body")
public boolean body(User user) {
return userService.addUser(user);
}
此时,就需要用到辅助工具来测试了,我用的是 Apifox(类似Postman)👇,请求参数与属性名相同自动填充到 user 对象中
⑦ @Requestbody自动解析 JSON 字符串封装到对象
这是前后端分离最常用的方式,前端传入一个json字符串,自动转换成pojo对象:
@RequestMapping("/requestBody")
public String requestBody(@RequestBody User user) {
return user.toString();
}
使用POST请求,发送端的 media type 设置为 json,数据是 json 字符串,请求成功 👇
5 【Spring Boot + MyBatis-Plus + JSP】项目实战
项目源码:尹煜 / SpringMvcJsp · GitCode
前后端未分离之前,页面跳转的工作都是由后端控制,采用JSP进行展示数据。虽然现在互联网项目几乎不会再使用JSP,但是我觉得还是需要学习一下,因为有些旧项目还是会用JSP,或者需要重构,有兴趣的可以看下~
首先提前看下最终的框架图 👇
5.1 JSP项目初始化
以下内容建立在第2章【搭建项目】的前提下,下边是针对 JSP 进行的一些操作,包括Dao层 、Service层等内容已在第2章完成,而且本项目意在精简,没有繁多的 xml 文件,一次配置即可,可维护性不错。
① 导入 jasper、jstl、servlet 等依赖
路径:pom.xml
<!--用于编译jsp-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<!--支持jstl-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!--servlet开启-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
② webapp 目录
Jsp 资源存放在 webapp 目录,需要对其进行创建及相关操作
Ⅰ首先创建如下目录
Ⅱ 然后在 IDEA 中设置 webapp 路径
点击进入到 Project Structure 页面
指定配置目录,这样的话,框架就会将 webapp 目录识别为资源文件
③ 配置视图解析器
路径:src/main/resources/application.properties
#项目名称
spring.application.name=jsp
#项目访问名称,如果不配置直接访问bean就可以
#server.servlet.context-path=/index
#端口
server.port=8080
#Spring boot视图配置
spring.mvc.view.prefix=/jsp/
spring.mvc.view.suffix=.jsp
#静态文件访问配置
spring.mvc.static-path-pattern=/static/**
注:server.servlet.context-path:应用的上下文路径,也可以称为项目路径,是构成url地址的一部分。不配置时,默认为 / ,如:localhost:8080/xxxxxx;配置时,比如 /demo,此时的访问方式为localhost:8080/demo/xxxxxx。
④ 测试配置是否成功
Ⅰ新增 index.jsp
index.jsp 默认为初始页,也就是说输入 localhost:8080 就会跳转到该页!
路径:src/main/webapp/index.jsp
<%--
Created by IntelliJ IDEA.
User: xiaowo
Date: 2022/11/27
Time: 9:45
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>yinyu!!!!!</h1>
</body>
</html>
Ⅱ 启动项目测试
输入网址:http://localhost:8080/ ,测试成功 👇
5.2 UserController类及视图层编写
首先展示下 webapp 的最终目录 👇,index.jsp 可以忽略(只用作测试)
① /user/homePage 接口及 homePage 视图
Ⅰ/user/homePage 接口
主要用到 ModelAndView ,将 homePage.jsp 注入,最后返回 homePage 视图
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserServiceImpl userService;
@RequestMapping(value="/homePage")
public ModelAndView homePage(){
//设置首页
ModelAndView mv = new ModelAndView();
mv.setViewName("homePage");
return mv;
}
//继续加接口
}
Ⅱ 编写首页 homePage .jsp
<%--
Created by IntelliJ IDEA.
User: xiaowo
Date: 2022/11/26
Time: 17:47
To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE HTML>
<html>
<head>
<title>首页</title>
<style type="text/css">
a {
text-decoration: none;
color: black;
font-size: 18px;
}
h3 {
width: 180px;
height: 38px;
margin: 100px auto;
text-align: center;
line-height: 38px;
background: deepskyblue;
border-radius: 4px;
}
</style>
</head>
<body>
<h3>
<%--调用 /user/allUser 接口--%>
<a href="${pageContext.request.contextPath}/user/allUser">点击进入列表页</a>
</h3>
</body>
</html>
② /user/allUser 接口及 allUser 视图
Ⅰ/user/allUser 接口
此处用到了查询操作,将返回的 List<User> 注入到 ModelAndView ,从而 allUser 视图能够取到。
@RequestMapping("/allUser")
public ModelAndView allUser(ModelAndView mv) {
//① 查询操作,//modelAndView 注入 list,以便页面调用
List<User> list = userService.queryAllUser();
mv.addObject("list", list);
//② modelAndView 注入 /jsp/allUser.jsp ,跳转至 /jsp/allUser.jsp页面
mv.setViewName("allUser");
return mv;
}
Ⅱ 编写用户详情页 allUser.jsp
<%--
Created by IntelliJ IDEA.
User: xiaowo
Date: 2022/11/26
Time: 20:26
To change this template use File | Settings | File Templates.
--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>用户列表</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<div class="page-header">
<h1>
<small>用户列表 —— 显示所有用户</small>
</h1>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4 column">
<a class="btn btn-primary" href="${pageContext.request.contextPath}/user/toAddUser">新增</a>
</div>
</div>
<div class="row clearfix">
<div class="col-md-12 column">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>用户ID</th>
<th>用户姓名</th>
<th>用户年龄</th>
<th>用户邮箱</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<c:forEach var="user" items="${requestScope.get('list')}">
<tr>
<td>${user.getId()}</td>
<td>${user.getName()}</td>
<td>${user.getAge()}</td>
<td>${user.getEmail()}</td>
<td>
<a href="${pageContext.request.contextPath}/user/toUpdateUser?id=${user.getId()}">更改</a>|
<a href="javascript:void(0)" onclick="confirmDel(${user.getId()})">删除</a>
<script type="text/javascript">
function confirmDel(param)
{
if(window.confirm("确定删除?")){
document.location="${pageContext.request.contextPath}/user/del/"+param
}
}
</script>
<%-- onclick的响应函数中还传递了一个参数param,我使用的是$标签,数据传到函数中还进行了一个字符串的拼接。--%>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
</div>
③ /user/toAddUser、/user/addUser 接口及 addUser 视图
Ⅰ/user/toAddUser、/user/addUser 接口
@RequestMapping("/toAddUser")
public ModelAndView toAddPaper() {
//跳转至 addUser 页面
ModelAndView mv = new ModelAndView();
mv.setViewName("addUser");
return mv;
}
@RequestMapping("/addUser")
public ModelAndView addUser(User user) {
boolean b = userService.addUser(user);
log.info("The outcome of adding User:{}",b);
//新增成功后跳转至 allUser 接口
ModelAndView mv = new ModelAndView();
mv.setViewName("redirect:/user/allUser");//controller接口跳转到另一个controller接口
return mv;
}
Ⅱ 编写用户新增页 addUser.jsp
<%--
Created by IntelliJ IDEA.
User: xiaowo
Date: 2022/11/26
Time: 21:37
To change this template use File | Settings | File Templates.
--%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>新增用户</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<div class="page-header">
<h1>
<small>新增用户</small>
</h1>
</div>
</div>
</div>
<form action="${pageContext.request.contextPath}/user/addUser" method="post">
用户姓名:<input type="text" name="name"><br><br><br>
用户年龄:<input type="number" name="age"><br><br><br>
用户邮箱:<input type="text" name="email"><br><br><br>
<input type="submit" value="添加">
</form>
</div>
④ /user/toUpdateUser、/user/updateUser 接口及 updateUser 视图
Ⅰ/user/toUpdateUser、/user/updateUser 接口
@RequestMapping("/toUpdateUser")
public ModelAndView toUpdateUser(int id, ModelAndView mv) {
User user = userService.queryUser(id);
log.info("The updating user -- {}",user);
mv.addObject("user",user);
//跳转至 uodateUser 页面
mv.setViewName("updateUser");
return mv;
}
@RequestMapping("/updateUser")
public ModelAndView updateUser(User user, ModelAndView mv) {
boolean b = userService.updateUser(user);
log.info("The outcome of updated User:{}",b);
//更新成功后跳转至 allUser 接口
mv.setViewName("redirect:/user/allUser");
return mv;
}
Ⅱ 编写用户跟新页 updateUser.jsp
<%--
Created by IntelliJ IDEA.
User: xiaowo
Date: 2022/11/26
Time: 22:10
To change this template use File | Settings | File Templates.
--%>
<jsp:useBean id="user" scope="request" class="com.yinyujsp.pojo.User"/>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>修改信息</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<div class="page-header">
<h1>
<small>修改信息</small>
</h1>
</div>
</div>
</div>
<form action="${pageContext.request.contextPath}/user/updateUser" method="post" >
<%-- 在最上边取到上一个接口传回的 user --%>
<input type="hidden" name="id" value="${user.getId()}"/>
用户姓名:<input type="text" name="name" value="${user.getName()}"/>
用户年龄:<input type="number" name="age" value="${user.getAge()}"/>
用户邮箱:<input type="text" name="email" value="${user.getEmail()}"/>
<input type="submit" value="提交"/>
</form>
</div>
⑤ /user/del/{userId}接口
使用了路径参数,该接口的实现在 allUser.jsp 里边,我还给它加了个删除确认弹窗~
@RequestMapping("/del/{userId}")
public ModelAndView deleteUser(@PathVariable("userId") int id) {
boolean b = userService.deleteUserById(id);
log.info("The outcome of deleted User:{}",b);
//删除成功后跳转至 allUser 接口
ModelAndView mv = new ModelAndView();
mv.setViewName("redirect:/user/allUser");
return mv;
}
5.3 项目展示
首页 👇
详情页 👇
用户新增页 👇
用户修改页 👇
删除确认弹窗 👇
5 拦截器
拦截器是重点内容了,很多时候都要用拦截器,比如登录校验,权限校验等等。
实现HandlerInterceptor接口,接口有三个方法需要重写。
- preHandle():在业务处理器处理请求之前被调用。预处理。
- postHandle():在业务处理器处理请求执行完成后,生成视图之前执行。后处理。
- afterCompletion():在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面);
5.1 自定义拦截器
路径:src/main/java/com/yinyujsp/config/DemoInterceptor.java
package com.yinyujsp.config;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DemoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//预处理,返回true则继续执行。如果需要登录校验,校验不通过返回false即可,通过则返回true。
System.out.println("执行preHandle()方法");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
//后处理
System.out.println("执行postHandle()方法");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//在DispatcherServlet完全处理完请求后被调用
System.out.println("执行afterCompletion()方法");
}
}
5.2 将拦截器注入到Spring容器中
路径:src/main/java/com/yinyujsp/config/ConverterConfig.java
package com.yinyujsp.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class ConverterConfig extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/**");///**代表所有路径
}
}
5.3 测试拦截器
run 启动类,请求如下接口 👇
控制台输出成功 👇
6 全局异常管理
SpringMVC 本身就对一些异常进行了全局处理,请看 HandlerExceptionResolver 接口的类图👇
从类图可以看出有四种异常处理器:
- DefaultHandlerExceptionResolver,默认的异常处理器。根据各个不同类型的异常,返回不同的异常视图。
- SimpleMappingExceptionResolver,简单映射异常处理器。通过配置异常类和view的关系来解析异常。
- ResponseStatusExceptionResolver,状态码异常处理器。解析带有@ResponseStatus注释类型的异常。
- ExceptionHandlerExceptionResolver,注解形式的异常处理器。对@ExceptionHandler注解的方法进行异常解析。
第一个默认的异常处理器是内置的异常处理器,是对一些常见的异常处理,一般来说不用管它,SimpleMappingExceptionResolver 的异常处理器,在如今前后端分离的环境下已经看不到了,所以本文针对扩展最后两个。
6.1 ResponseStatusExceptionResolver
这种异常处理器主要用于处理带有 @ResponseStatus 注释的异常。
①自定义一个异常类,并且使用 @ResponseStatus 注解修饰 👇
路径:src/main/java/com/yinyujsp/config/DefinedException.java
package com.yinyujsp.config;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
//HttpStatus枚举有所有的状态码,这里返回一个400的响应码
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class DefinedException extends Exception{
}
② 编写 Controller 接口进行测试
路径:src/main/java/com/yinyujsp/controller/demoController.java
@RequestMapping("/defined")
public String defined(String msg) throws Exception {
if ("defined".equals(msg)) {
throw new DefinedException();
}
return "index";
}
启动项目,请求接口后响应成功 👇
6.2 ExceptionHandlerExceptionResolver
注解形式的异常处理器,目前来说最为常见,也十分方便。
① 定义自定义异常BaseException
路径:src/main/java/com/yinyujsp/config/BaseException.java
package com.yinyujsp.config;
public class BaseException extends Exception {
public BaseException(String message) {
super(message);
}
}
② 定义错误提示实体类ErrorInfo
路径:src/main/java/com/yinyujsp/pojo/ErrorInfo.java
package com.yinyujsp.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ErrorInfo {
public static final Integer OK = 0;
public static final Integer ERROR = -1;
private Integer code;
private String message;
private String url;
}
③ 定义全局异常处理类 GlobalExceptionHandler
路径:src/main/java/com/yinyujsp/config/GlobalExceptionHandler.java
package com.yinyujsp.config;
import com.yinyujsp.pojo.ErrorInfo;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
//这里使用了RestControllerAdvice,是@ResponseBody和@ControllerAdvice的结合
//会把实体类转成JSON格式的提示返回,符合前后端分离的架构
@RestControllerAdvice
public class GlobalExceptionHandler {
//这里自定义了一个BaseException,当抛出BaseException异常就会被此方法处理
@ExceptionHandler(BaseException.class)
public ErrorInfo errorHandler(HttpServletRequest req, BaseException e) throws Exception {
ErrorInfo r = new ErrorInfo();
r.setMessage(e.getMessage());
r.setCode(ErrorInfo.ERROR);
r.setUrl(req.getRequestURL().toString());
return r;
}
}
④ 编程测试接口进行测试
路径:src/main/java/com/yinyujsp/controller/demoController.java
@RequestMapping("/base")
public String base(String msg) throws Exception {
if ("base".equals(msg)) {
throw new BaseException("测试抛出BaseException异常~");
}
return "index";
}
启动项目,请求该接口响应成功 👇