【Java开发】 Spring 06 :Spring MVC 实践详解(Spring Boot+MyBatis-Plus+JSP 项目)

Spring MVC 是 Spring Framework 提供的 Web 组件,是目前主流的实现 MVC 设计模式的框架,提供前端路由映射、视图解析等功能,Java Web 是开发者必须要掌握的技术框架。请注意本文使用 Spring Boot 2.7.6 进行演示,直接使用 spring-boot-starter-web 依赖,同时也默认前后端分离,本文重点在于第5章的【Spring Boot + MyBatis-Plus + JSP】项目实战。

目录

1 Spring MVC 介绍

1.1 MVC 是什么

1.2 Spring MVC 核心组件

1.3 Spring MVC 工作流程

2 搭建项目

2.1 Mysql 数据库初始化

① 新建数据库

② 数据库 Schema 脚本

③ 数据库 Data 脚本

④ 使用 SQL 编辑器运行脚本

⑤ id 主键设置自增

2.2 Spring Boot 项目初始化

① 通过 IDEA Spring Initializr 创建项目

② 添加 Spring Web(最关键)等依赖

③ 导入 mybatis-plus 依赖

④ 连接数据库配置

⑤ 编写数据库对应的实体类

3 mybatis-plus 编写 Dao层 + Service层 

3.1 Dao层的 Mapper接口

① 编写实体类对应的mapper接口

② 在主启动类添加@MapperScan注解

3.2 Service层的 Iservice 接口和实现类

① 编写实体类对应的 UserBaseService 接口

② Service 层的实现类

4 定义 Controller

4.1 使用 @RequestMapping

① 支持Restful风格

② 支持Ant风格

4.2 接收参数

① 普通参数

② @RequestParam参数名绑定

③ @PathVariable路径参数

④ @RequestHeader绑定请求头属性

⑤ @CookieValue绑定请求的Cookie值

⑥ 绑定请求参数到实体类对象

⑦ @Requestbody自动解析 JSON 字符串封装到对象

5 【Spring Boot + MyBatis-Plus + JSP】项目实战

5.1 JSP项目初始化

① 导入 jasper、jstl、servlet 等依赖

② webapp 目录

③  配置视图解析器

④  测试配置是否成功

5.2 UserController类及视图层编写

① /user/homePage 接口及 homePage 视图

② /user/allUser 接口及 allUser 视图

③ /user/toAddUser、/user/addUser 接口及 addUser 视图

④ /user/toUpdateUser、/user/updateUser 接口及 updateUser 视图

⑤ /user/del/{userId}接口

5.3 项目展示

5 拦截器

5.1 自定义拦截器

5.2 将拦截器注入到Spring容器中

5.3 测试拦截器

6 全局异常管理

6.1 ResponseStatusExceptionResolver

①自定义一个异常类,并且使用 @ResponseStatus 注解修饰 👇

② 编写 Controller 接口进行测试

6.2 ExceptionHandlerExceptionResolver

① 定义自定义异常BaseException

② 定义错误提示实体类ErrorInfo

③ 定义全局异常处理类 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 工作流程

  1. 客户端请求被 DispatcherServlet 接收
  2. 根据 HandlerMapping 映射到 Handler
  3. 生成 Handler 和 HandlerInterceptor
  4. Handler 和 HandlerInterceptor 以 HandlerExecutionChain 的形式一并返回给 DispatcherServlet
  5. DispatcherServlet 通过 HandlerAdapter 调用 Handler 的方法完成业务逻辑处理
  6. 返回一个 ModelAndView 对象给 DispatcherServlet
  7. DispatcherServlet 把获取的 ModelAndView 对象传给 ViewResolver 视图解析器,把逻辑视图解析成物理视图
  8. ViewResolver 返回一个View进行视图渲染(把模型填充到视图中)
  9. 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";
    }

启动项目,请求该接口响应成功 👇


参考文章

狂神说SpringMVC05:整合SSM框架

5千字的SpringMVC总结,我觉得你会需要! - 知乎

springBoot+JSP搭建项目_那清澈的漓江的博客-CSDN博客_springboot+jsp

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尹煜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值