Spring Boot整合视图层,持久层,服务校验数据,异常处理-----F02

Spring Boot****整合视图层技术*

这一节我们主要学习如何整合视图层技术:

  • Jsp

  • Freemarker

  • Thymeleaf

在之前的案例中,我们都是通过 @RestController 来处理请求,所以返回的内容为json对象。那么如果需要渲染html页面的时候,要如何实现呢?

Spring Boot推荐使用模板引擎

模板引擎实现伪html 达到seo优化 使动态页面静态化

在动态html上实现Spring Boot依然可以完美胜任,并且提供了多种模板引擎的默认配置支持,所以在推荐的模板引擎下,我们可以很快的上手开发动态网站。

Spring Boot提供了默认配置的模板引擎主要有以下几种:

  • Thymeleaf
  • FreeMarker
  • Velocity
  • Groovy
  • Mustache

Spring Boot建议使用这些模板引擎,避免使用jsp。

1.Jsp

创建项目

创建 war 项目,编写pom.xml

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.6.RELEASE</version>
  </parent>

  <dependencies>
    <!-- Web 组件 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

视图解析器

resources/application.properties

#配置视图解析器
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

实体类

User.java

public class User implements Serializable {

    private Integer id;
    private String username;
    private Integer age;

    public User() {
    }

    public User(Integer id, String username, Integer age) {
        this.id = id;
        this.username = username;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", age=" + age +
                '}';
    }
    

控制层

UserController.java

@Controller
public class UserController {

    @RequestMapping("/showUser")
    public String showUser(Model model) {
        List<User> list = new ArrayList<>();
        list.add(new User(1, "张三", 18));
        list.add(new User(2, "李四", 20));
        list.add(new User(3, "王五", 22));
        model.addAttribute("list", list);
        // 跳转视图
        return "userList";
    }

}

视图层

userList.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>用户展示</title>
</head>
<body>
    <table border="1" cellspacing="0" align="center" width="50%">
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Age</th>
        </tr>
        <c:forEach items="${list}" var="user">
            <tr>
                <td>${user.id}</td>
                <td>${user.username}</td>
                <td>${user.age}</td>
            </tr>
        </c:forEach>
    </table>
</body>
</html>

启动类

App.java

@SpringBootApplication
public class App {

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

}

结果

image.png
2.Freemarker

创建项目

创建 war 项目,编写pom.xml

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
    </parent>

  <dependencies>
    <!-- Web 组件 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

视图解析器

该文件内容不一定非得需要,可以不添加,不添加使用默认值。

resources/application.properties

实体类

User.java

同上user实体类

控制层

UserController.java

@Controller
public class UserController {

    @RequestMapping("/showUser")
    public String showUser(Model model) {
        List<User> list = new ArrayList<>();
        list.add(new User(1, "张三", 18));
        list.add(new User(2, "李四", 20));
        list.add(new User(3, "王五", 22));
        model.addAttribute("list", list);
        // 跳转视图
        return "userList";
    }

}

视图层

Spring Boot要求模板形式的视图层技术的文件必须要放到 src/main/resources 目录下的 templates 目录。

该目录内的模板文件名称必须是 ftl 的后缀结尾。

userList.ftl

<!DOCTYPE html>
<html>
<head>
    <title>用户展示</title>
    <meta charset="UTF-8"></meta>
</head>
<body>
<table border="1" cellspacing="0" align="center" width="50%">
    <caption>用户信息</caption>
    <tr>
        <th>ID</th>
        <th>Name</th>
        <th>Age</th>
    </tr>
    <#list list as user >
        <tr>
            <td>${user.id}</td>
            <td>${user.username}</td>
            <td>${user.age}</td>
        </tr>
    </#list>
</table>
</body>
</html>

启动类

App.java

@SpringBootApplication
public class App {

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

}

结果

image.png
3.Thymeleaf (重点讲解)

入门案例

创建项目

创建 war 项目,编写pom.xml

    <!-- thymeleaf 组件 -->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
      </dependency>

控制层

ThymeleafController.java

@Controller
public class ThymeleafController {

    @RequestMapping("/show")
    public String show(Model model) {
        model.addAttribute("message", "Thymeleaf 入门");
        return "msg";
    }

}


视图层

Spring Boot要求模板形式的视图层技术的文件必须要放到 src/main/resources 目录下的 templates 目录。

msg.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Thymeleaf入门</title>
</head>
<body>
    <span th:text="${message}"></span>
    <input th:value="${message}"/>
</body>
</html>

启动类

App.java

@SpringBootApplication
public class App {

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

}

image.png

运行结果出现异常(如果是 Spring Boot 1.X.X 版本才会出现此异常):

1   org.xml.sax.SAXParseException: 元素类型 "meta" 必须由匹配的结束标记 "</meta>" 终止。

异常处理

如果是 Spring Boot 1.X.X 版本需要以下操作,本案例是 Spring Boot 2.1.6 版本,所以不需要更改。

方式一:编写风格严谨的HTML代码

1 <meta charset="UTF-8"/>

**方式二:更换hymeleaf的****jar包版本

thymeleaf.jar:更新为 3.0 以上的版本

<properties>
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version> 
</properties>


运行结果

image.png

Thymeleaf 语法详解

Thymeleaf 内置对象语法:

  • 调用内置对象一定要用 #
  • 大部分的内置对象都以 s 结尾 strings 、 numbers 、 dates

准备数据

实体类

User.java

public class User implements Serializable {

    private Integer id;
    private String username;
    private Integer age;

    public User() {
    }

    public User(Integer id, String username, Integer age) {
        this.id = id;
        this.username = username;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", age=" + age +
                '}';
    }
    
}

控制层

ThymeleafController.java

@Controller
public class ThymeleafController {

    @RequestMapping("/show")
    public String showMsg(Model model,
                          HttpServletRequest request,
                          HttpServletResponse response) {

        // 字符串
        model.addAttribute("msg", "Thymeleaf 入门案例");
        // 日期时间
        model.addAttribute("myDate", new Date());
        // 条件判断if
        model.addAttribute("sex", 1);
        // 条件判断switch
        model.addAttribute("id", 1);
        // 对象
        model.addAttribute("user", new User(1, "张三", 20));

        // 迭代遍历list
        List<User> userList = new ArrayList<>();
        userList.add(new User(1, "张三", 20));
        userList.add(new User(2, "李四", 22));
        userList.add(new User(3, "王五", 24));
        model.addAttribute("userList", userList);

        // 迭代遍历map
        Map<String, User> userMap = new HashMap<>();
        userMap.put("u1", new User(1, "张三", 20));
        userMap.put("u2", new User(2, "李四", 22));
        userMap.put("u3", new User(3, "王五", 24));
        model.addAttribute("userMap", userMap);

        // 域对象操作
        request.setAttribute("req", "HttpServletRequest");
        request.getSession().setAttribute("sess", "HttpSession");
        request.getSession().getServletContext().setAttribute("app", "Application");
        return "msg";

    }

    /**
     * URL表达式-相对路径
     * @return
     */
    @RequestMapping("/index")
    public String index() {
        return "index";
    }

    /**
     * URL表达式-普通传参
     * @param id
     * @param username
     * @return
     */
    @RequestMapping("/user")
    public String user(Integer id, String username) {
        System.out.println("id:" + id + " username:" + username);
        return "user";
    }

    /**
     * URL表达式-restful传参
     * @param id
     * @param username
     * @return
     */
    @RequestMapping("/person/{id}/{username}")
    public String person(@PathVariable Integer id, @PathVariable String username) {
        System.out.println("id:" + id + " username:" + username);
        return "person";
    }

}

视图层

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Thymeleaf</title>
</head>
<body>
index
</body>
</html>

user.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Thymeleaf</title>
</head>
<body>
user
</body>
</html>

person.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Thymeleaf</title>
</head>
<body>
person
</body>
</html>

字符串

字符串操作

${#strings.isEmpty(key)} :判断字符串是否为空

${#strings.contains(msg, 'T')} :判断字符串是否包含子串

${#strings.startsWith(msg, 'T')} :判断字符串是否以子串开头

${#strings.endsWith(msg, 'T')} :判断字符串是否以子串结尾

``

${#strings.length(msg)} :返回字符串的长度

``

${#strings.indexOf(msg, 'T')} :查找子串的位置,并返回该子串的下标,如果没找到则返回-1

${#strings.substring(msg, 5)} :截取子串,从指定下标开始截止到末尾结束

${#strings.substring(msg, 5, 12)} :截取子串,从指定下标开始截止到指定下标结束

``

${#strings.toUpperCase(msg)} :将字符串转大写

${#strings.toLowerCase(msg)} :将字符串转小写

日期时间

${#dates.format(key)} :格式化日期,以浏览器默认语言为格式化标准

${#dates.format(key,'yyy/MM/dd')} :自定义格式日期转换

${#dates.year(myDate)} :获取年份,还可以获取月份、日、时、分、秒

条件判断

th:if :单选择

性别:<span th:if="${sex} == 1">男</span>
<span th:if="${sex} == 2">女</span>

th:switch :多选择(如果要实现 if else if else 判断表达式,在 Thymeleaf 要使用 th:switch 代替)

编号:<span th:switch="${id}">
<span th:case="1">1 张三</span>
<span th:case="2">2 李四</span>
<span th:case="3">3 王五</span>
</span>

对象

<input th:value="${user.username}"/>
<span th:text="${user.age}"></span>

迭代遍历

th:each :迭代遍历

th:each :状态变量属性

  • ​ index:当前迭代器的索引 从 0 开始
  • ​ count:当前迭代对象的计数 从 1 开始
  • ​ size:被迭代对象的长度
  • ​ even/odd:布尔值,当前循环是否是偶数/奇数 从 0 开始
  • first:布尔值,当前循环的是否是第一条,如果是返回 true 否则返回 false
  • last:布尔值,当前循环的是否是最后一条,如果是则返回 true 否则返回 false

th:each :迭代 Map

<table border="1" cellspacing="0">
        <tr>
            <th>Id</th>
            <th>Name</th>
            <th>Age</th>
        </tr>
        <tr th:each="user : ${userMap}">
            <td th:text="${user}"></td>
        </tr>
    </table>
    <table border="1" cellspacing="0">
        <tr>
            <th>Id</th>
            <th>Name</th>
            <th>Age</th>
        </tr>
        <tr th:each="user : ${userMap}">
            <td th:each="entry : ${user}" th:text="${entry.value.id}" ></td>
            <td th:each="entry : ${user}" th:text="${entry.value.username}"></td>
            <td th:each="entry : ${user}" th:text="${entry.value.age}"></td>
        </tr>
    </table>

域对象操作

${#httpServletRequest.getAttribute(key)} :HttpServletRequest

``

${session.key} :HttpSession

``

${application.key} :ServletContext

URL****表达式

基本语法

URL表达式的基本语法: @{}

th:href :绝对路径

<a th:href="@{http://www.baidu.com}">绝对路径</a>

th:href :相对路径,相对于当前项目的根路径

<a th:href="@{/index}">相对于当前项目的根路径</a>

th:href :相对路径, 相对于服务器的根路径

<a th:href="@{~/project/resourcename}">相对于服务器的根路径</a>

参数传递

   <a th:href="@{/user(id=1, username=zhagnsan)}">相对路径-普通传参</a>
    <a th:href="@{/person/1/zhangsan}">相对路径-restful传参</a>
    <a th:href="@{/person/{id}/{name}(id=2, name=lisi)}">相对路径-restful传参</a>


Spring Boot****整合持久层技术
1.MyBatis

创建项目

image.png

创建 jar项目,编写pom.xml

 <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.7.RELEASE</version>
  </parent>


  <dependencies>
    <!-- web 组件 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- thymeleaf 组件 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <!-- mybatis 组件 -->
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>2.1.0</version>
    </dependency>
    <!-- mysql 数据库驱动 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!-- druid 数据库连接池 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.19</version>
    </dependency>
  </dependencies>

  <!-- build标签 常用于添加插件及编译配置 -->
  <build>
    <!-- 读取配置文件 -->
    <resources>
      <resource>
        <directory>src/main/resources</directory>
      </resource>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
          <include>**/*.properties</include>
          <include>**/*.tld</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
  </build>

properties 配置文件

添加 application.properties 全局配置文件

# 配置数据库驱动
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=shsxt
# 配置数据库连接池
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# 配置mybatis数据返回类型别名(默认别名是类名)
mybatis.type-aliases-package=com.springboot.pojo
# 配置mybatis.Maper映射文件
mybatis.mapper-locations=classpath:com/springboot/mapper/*.xml


SQL****文件

创建一个test表,字段id,name ,age;

    CREATE TABLE `user` (

`id` INT (11) NOT NULL AUTO_INCREMENT,

`name` VARCHAR (255) DEFAULT NULL,

`age` INT (11) DEFAULT NULL,

5PRIMARY KEY (`id`)

   ) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;


1.1添加用户

实体类

User.java

public class User implements Serializable {

    private Integer id;
    private String name;
    private Integer age;

    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 Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

Mapper 接口

UserMapper.java

public interface UserMapper {

    // 添加用户
    int insertUser(User user);

    // 查询用户
    List<User> selectUserList();

    // 根据主键查询用户
    User selectUserById(Integer id);

    // 修改用户
    int updateUser(User user);

    // 删除用户
    int deleteUser(Integer id);

}

映射配置文件

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace 必须是接口的完全限定名 -->
<mapper namespace="com.springboot.mapper.UserMapper">

    <!-- id 必须和接口中的方法名一致 -->
    <insert id="insertUser" parameterType="user">
        insert into user (name, age) values (#{name}, #{age})
    </insert>

    <!-- 查询所有用户 -->
    <select id="selectUserList" resultType="user">
        select id, name, age from user;
    </select>

    <!-- 根据主键查询用户 -->
    <select id="selectUserById" resultType="user">
        select id, name, age from user where id = #{id};
    </select>

    <!-- 修改用户 -->
    <update id="updateUser" parameterType="user">
        update user set name = #{name}, age = #{age} where id = #{id};
    </update>

    <!-- 删除用户 -->
    <delete id="deleteUser">
        delete from user where id = #{id}
    </delete>

</mapper>

业务层

UserServiceI.java

public interface UserServiceI {

    int insertUser(User user);

    // 查询用户
    List<User> selectUserList();

    // 根据主键查询用户
    User selectUserById(Integer id);

    // 修改用户
    int updateUser(User user);

    // 删除用户
    int deleteUser(Integer id);

}

UserServiceImpl.java

@Service
@Transactional
public class UserServiceImpl implements UserServiceI {

    @Autowired
    private UserMapper userMapper;

    @Override
    public int insertUser(User user) {
        return userMapper.insertUser(user);
    }

    @Override
    public List<User> selectUserList() {
        return userMapper.selectUserList();
    }

    @Override
    public User selectUserById(Integer id) {
        return userMapper.selectUserById(id);
    }

    @Override
    public int updateUser(User user) {
        return userMapper.updateUser(user);
    }

    @Override
    public int deleteUser(Integer id) {
        return userMapper.deleteUser(id);
    }

}


控制层

UserController.java

/**
 * 操作持久层
 */
@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserServiceI userService;

    /**
     * 页面跳转
     */
    @RequestMapping("/{page}")
    public  String page(@PathVariable String page){
        return page;
    }
    /**
     * 添加用户
     */
    @PostMapping("/insertUser")
    public String insertUser(User user){
        int result =userService.insertUser(user);
        return "success";
    }

    /**
     * 查询用户列表
     */
    @GetMapping("/selectUserList")
    public String selectUserList(Model model) {
        model.addAttribute("userList", userService.selectUserList());
        return "user-list";
    }

    /**
     * 根据主键查询
     */
    @GetMapping("/edit/{id}")
    public String edit(@PathVariable Integer id, Model model) {
        model.addAttribute("user", userService.selectUserById(id));
        return "updateUser";
    }
    /**
     * 修改用户保存
     * 修改之前需要先根据ID查询
     */
    @PostMapping("/updateUser")
    public String updateUser(User user) {
        userService.updateUser(user);
        return "success";
    }

    /**
     * 删除用户
     */
    @GetMapping("/deleteUser/{id}")
    public String deleteUser(@PathVariable Integer id) {
        userService.deleteUser(id);
        return "success";
    }

}


视图层

templates/register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册用户</title>
</head>
<body>
    <form th:action="@{/user/insertUser}" method="post">
        姓名:<input name="name"/><br/>
        年龄:<input name="age"/><br/>
        <input type="submit" value="注册"/>
    </form>
</body>
</html>

templates/success.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>提示</title>
</head>
<body>
    成功
</body>
</html>

启动类

App.java

@SpringBootApplication
@MapperScan("com.springboot.mapper")
public class App {

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

}

结果

页面请求

image.png

数据库

image.png

1.2查询用户

视图层

templates/user-list.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户列表</title>
</head>
<body>
    <table border="1" width="300px" cellspacing="0">
        <tr>
            <th>ID</th>
            <th>NAME</th>
            <th>AGE</th>
            <th>操作</th>
        </tr>
        <tr th:each="user : ${userList}">
            <td th:text="${user.id}"></td>
            <td th:text="${user.name}"></td>
            <td th:text="${user.age}"></td>
            <td>
                <a th:href="@{/user/edit/{id}(id=${user.id})}">修改</a>
                <a th:href="@{/user/deleteUser/{id}(id=${user.id})}">删除</a>
            </td>
        </tr>
    </table>
</body>
</html>

结果

查询所有用户

image.png

根据主键查询用户

image.png

1.3修改用户

视图层

templates/updateUser.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>修改用户</title>
</head>
<body>
    <form th:action="@{/user/updateUser}" method="post">
        <input type="hidden" name="id" th:value="${user.id}"/>
        姓名:<input name="name" th:value="${user.name}"/><br/>
        年龄:<input name="age" th:value="${user.age}"/><br/>
        <input type="submit" value="修改"/>
    </form>
</body>
</html>

结果

查询用户,并填写修改信息

image.png

数据库

image.png

1.4删除用户

结果

查询用户列表

image.png

数据库

image.png

Spring Boot****服务端数据校验

项目中使用较多的是前端的校验,比如页面中js校验。对于安全较高的建议在服务端进行校验。

校验框架

Spring 使用 Hibernate 的校验框架 validator,页面提交的请求参数,请求到 controller 方法中,使用 validator 进行校验,如果校验不通过,将错误信息展示到页面。

image.png

主要使用到以下相关 jar 包(蓝色部分)


image.png
1.数据校验

创建项目

使用 IntelliJ IDEA 2019.1 直接创建Spring Boot项目

image.png

目前最新的稳定版 Spring Boot 是2.1.7,我们课程中使用的2.1.6,所以需要修改一下pom.xml的版本信息。在创建项目时勾选 Web 和 Themeleaf 组件。

image.png
image.png

pom.xml

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.springboot</groupId>
    <artifactId>springboot-validator</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-validator</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

编写添加用户功能

实体类

User.java

package com.springboot.pojo;
import java.io.Serializable;
public class User implements Serializable {
private Integer id;

private String username;

private String password;

private Integer age;

private String email;
public Integer getId() {

return id;

}
public void setId(Integer id) {

this.id = id;

}



public String getUsername() {

return username;

    }



public void setUsername(String username) {

    this.username = username;

    }


public String getPassword() {

    return password;

    }



public void setPassword(String password) {

    this.password = password;

    }



    public Integer getAge() {

    return age;
 
    }

    

    public void setAge(Integer age) {

    this.age = age;

    }

    

    public String getEmail() {

    return email;

    }

    

    public void setEmail(String email) {

    this.email = email;

    }



    @Override

    public String toString() {

    return "User{" +

    "id=" + id +
    ", username='" + username + '\'' +
    ", password='" + password + '\'' +
    ", age=" + age +
    ", email='" + email + '\'' +
    '}';

    }   
}


控制层

UserController.java

@Controller
@RequestMapping("/user")
public class UserController {
/**
* 页面跳转
* *
@param page
* @return
*/
@RequestMapping("/{page}")
public String page(@PathVariable String page) {
return page;
} /
**
* 添加用户
* *
@param user
* @param model
* @return
*/
@PostMapping("/insertUser")
public String insertUser(User user, Model model) {
model.addAttribute("user", user);
return "success";
}
}

视图层

templates/register.html

用戶注冊頁面

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加用户</title>
</head>
<body>
<form th:action="@{/user/insertUser}" method="post">
姓名:<input type="text" name="username"/><br/>
密码:<input type="text" name="password"/><br/>
年龄:<input type="text" name="age"/><br/>
邮箱:<input type="text" name="email"/><br/>
<input type="submit" value="注册"/><br/>
</form>
</body>
</html>

templates/success.html

注册成功页面,返回user

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>提示</title>
</head>
<body>
<span th:text="${user}"></span>
</body>
</html>

启动类

App.java

准备好项目后,启动项目测试无误,准备开始数据校验.

image.png

2.数据校验步骤

实体类添加校验规则

User.java

public class User implements Serializable {

    private Integer id;
    @NotEmpty(message = "用户名不可以为空", groups = {ValidateGroupForName.class, ValidateGroupForAll.class})// 非空校验不会去除前后空格
    private String username;
    @NotBlank(message = "密码不可以为空", groups = {ValidateGroupForAll.class})// 非空校验会去除前后空格
    @Size(message = "且长度在6 ~ 10之间", min = 6, max = 10, groups = {ValidateGroupForAll.class})// 长度必须在6~10之间
    private String password;
    // 校验是否为数字,并且在规定的大小值内
    @Max(message = "年龄必须在1 ~ 150岁之间", value = 150, groups = {ValidateGroupForAll.class})// 必须小于等于指定的值
    @Min(message = "年龄必须在1 ~ 150岁之间", value = 1, groups = {ValidateGroupForAll.class})// 必须大于等于指定的值
    private Integer age;
    // 校验邮箱格式,默认正则匹配规则是 ".*"
    @Email(message = "请输入正确的邮箱格式:xx@xx.com", regexp = "[a-za-z0-9._%+-]+@[a-za-z0-9.-]+\\.[a-za-z]{2,4}",
            groups = {ValidateGroupForAll.class})
    private String email;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                '}';
    }
    
}

控制层开启校验

UserController.java

@Controller
@RequestMapping("/user")
public class UserController {

    /**
     * 页面跳转
     *
     * @param page
     * @return
     */
    @RequestMapping("/{page}")
    public String page(@PathVariable String page, User user) {
        return page;
    }

    /**
     * 添加用户
     *
     * @param user  @Validated:表示对哪个参数进行校验
     *              BindingResult:接收校验提示信息
     *              @Validated 和 BindingResult 是配对出现,并且形参顺序是固定的
     * @param model
     * @return
     */
    @PostMapping("/insertUser")
    public String insertUser(@Validated(value = {ValidateGroupForAll.class}) User user, BindingResult bindingResult, Model model) {
        model.addAttribute("user", user);
        if (bindingResult.hasErrors()) {
            List<ObjectError> errors = bindingResult.getAllErrors();
            for (ObjectError error: errors) {
                System.out.println(error.getDefaultMessage());// 错误信息
            }
        }
        return bindingResult.hasErrors() ? "register" : "success";
    }

    /**
     * 用户登录。只校验用户名
     * @param user
     * @param bindingResult
     * @return
     */
    @PostMapping("/login")
    public String login(@Validated(value = {ValidateGroupForName.class}) User user, BindingResult bindingResult) {
        return bindingResult.hasErrors() ? "login" : "success";
    }

}

视图层获取提示信息

templates/register.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>添加用户</title>
</head>
<body>
<form th:action="@{/user/insertUser}" method="post">
    姓名:<input type="text" name="username"/>
    <span style="color: red;" th:errors="${user.username}"></span><br/>
    密码:<input type="text" name="password"/>
    <span style="color: red;" th:errors="${user.password}"></span><br/>
    年龄:<input type="text" name="age"/>
    <span style="color: red;" th:errors="${user.age}"></span><br/>
    邮箱:<input type="text" name="email"/>
    <span style="color: red;" th:errors="${user.email}"></span><br/>
    <input type="submit" value="注册"/><br/>
</form>
</body>
</html>

*运行发生异常

org.thymeleaf.exceptions.TemplateProcessingException: Error during
execution of processor
'org.thymeleaf.spring5.processor.SpringErrorsTagProcessor' (template:
"register" - line 10, col 27)
at
org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(Ab
stractAttributeTagProcessor.java:117) ~[thymeleaf-
3.0.11.RELEASE.jar:3.0.11.RELEASE]

解决数据校验异常

方法一

在跳转页面的方法中注入一个对象,要求对象参数的变量名必须是类名的全称且首字母小写(小驼峰)。

UserController.java

@Controller
@RequestMapping("/user")
public class UserController {
/**
* 页面跳转
* *
@param page
* @return
*/
@RequestMapping("/{page}")
public String page(@PathVariable String page, User user) {
return page;
} /
**
* 添加用户
* *
@param user @Validated:表示对哪个参数进行校验
* BindingResult:接收校验提示信息
* @Validated 和 BindingResult 是配对出现,并且形参顺序是固定的
* @param model
* @return
*/
@PostMapping("/insertUser")
public String insertUser(@Validated User user, BindingResult
bindingResult, Model model) {
return bindingResult.hasErrors() ? "register" : "success";
}
}

然后页面使用该变量名获取校验提示信息

templates/register.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加用户</title>
</head>
<body>
<form th:action="@{/user/insertUser}" method="post">
姓名:<input type="text" name="username"/>
<span style="color: red;" th:errors="${user.username}"></span><br/>
密码:<input type="text" name="password"/>
<span style="color: red;" th:errors="${user.password}"></span><br/>
年龄:<input type="text" name="age"/>
<span style="color: red;" th:errors="${user.age}"></span><br/>
邮箱:<input type="text" name="email"/>
<span style="color: red;" th:errors="${user.email}"></span><br/>
<input type="submit" value="注册"/><br/>
</form>
</body>
</html>

方法二

使用 @ModelAttribute 注解,并且可以改变页面获取校验信息参数名称。

UserController.java

@Controller
@RequestMapping("/user")
public class UserController {
/**
* 页面跳转
* *
@param page
* @return
*/
@RequestMapping("/{page}")
public String page(@PathVariable String page, @ModelAttribute("u") User
user) {
return page;
} /
**
* 添加用户
* *
@param user @Validated:表示对哪个参数进行校验
* BindingResult:接收校验提示信息
* @Validated 和 BindingResult 是配对出现,并且形参顺序是固定的
* @param model
* @return
*/
@PostMapping("/insertUser")
public String insertUser(@ModelAttribute("u") @Validated User user,
BindingResult bindingResult, Model model) {
return bindingResult.hasErrors() ? "register" : "success";
}
}

页面使用 u 获取校验提示信息

templates/register.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加用户</title>
</head>
<body>
<form th:action="@{/user/insertUser}" method="post">
姓名:<input type="text" name="username"/>
<span style="color: red;" th:errors="${u.username}"></span><br/>
密码:<input type="text" name="password"/>
<span style="color: red;" th:errors="${u.password}"></span><br/>
年龄:<input type="text" name="age"/>
<span style="color: red;" th:errors="${u.age}"></span><br/>
邮箱:<input type="text" name="email"/>
<span style="color: red;" th:errors="${u.email}"></span><br/>
<input type="submit" value="注册"/><br/>
</form>
</body>
</html>

运行结果

image.png

自定义校验提示信息

我们可以通过 message 属性来自定义校验提示信息。

结果


image.png

3.分组校验

当需要不同的方法对同一个对象进行校验,但是每个方法需要不同的校验时,需要将校验规则分组。

定义校验分组

image.png

ValidateGroupForName.java

package com.springboot.group;

public interface ValidateGroupForName {
    // 接口中不需要定义任何方法,仅是对不同的校验规则进行分组
    // 此分组只校验姓名
}

ValidateGroupForAll.java

package com.springboot.group;

public interface ValidateGroupForAll {
    // 接口中不需要定义任何方法,仅是对不同的校验规则进行分组
    // 此分组校验全部
}

User.java

通过注解配置添加分组

每个方法使用不同的校验分组

此时,添加用户方法根据 ValidateGroupForName 分组校验,只会校验用户名,结果如下。

image.png
Spring Boot****异常处理

Spring Boot对于异常处理提供了五种处理方式。

创建Spring Boot项目,选择 Web 和 Thymeleaf 组件。

1.默认

[图片上传失败...(image-d5c852-1566671305409)]

Spring Boot默认的处理异常的机制是:一旦程序中出现了异常Spring Boot会向 /error 的 url 发送请求。在Spring Boot中提供了一个叫 BasicExceptionController 来处理 /error 请求,然后跳转到默认显示异常的页面来展示异常信息。

image.png

如果我们需要将所有的异常统一跳转到错误页面显式,需要在 src/main/resources/templates 目录下创建error.html 页面,Spring Boot会自动找到该页面作为错误页面。注意:名称必须叫 error

Spring Boot错误视图提供了以下错误属性:

  • timestamp:错误发生时间;
  • status:HTTP状态码;
  • error:错误原因;
  • exception:异常的类名;
  • message:异常消息(如果这个错误是由异常引起的);
  • errors:BindingResult异常里的各种错误(如果这个错误是由异常引起的);
  • trace:异常跟踪信息(如果这个错误是由异常引起的);
  • path:错误发生时请求的URL路径。

Spring Boot使用的前端框架模板不同,页面的名称也有所不同:

实现Spring的View接口的Bean,其ID需要设置为error(由Spring的BeanNameViewResolver所解析);

  • 如果配置了Thymeleaf,则需命名为error.html的Thymeleaf模板;
  • 如果配置了FreeMarker,则需命名为error.ftl的FreeMarker模板;
  • 如果配置了Velocity,则需命名为error.vm的Velocity模板;
  • 如果是用JSP视图,则需命名为error.jsp的JSP模板。

Thymeleaf 案例如下:

HelloController.java

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String hello() {
        Integer.parseInt("d");
        return "hello";
    }

}


编写错误跳转页面error.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>错误提示</title>
</head>
<body>
    <p>error页面</p>
    <p>亲,出错了,请稍后再试!</p>
    <p>错误发生时间:<span th:text="${timestamspan}"></span></p>
    <p>HTTP状态码:<span th:text="${status}"></span></p>
    <p>错误原因:<span th:text="${error}"></span></p>
    <p>异常的类名:<span th:text="${excespantion}"></span></p>
    <p>异常消息:<span th:text="${message}"></span></p>
    <p>BindingResult异常里的各种错误:<span th:text="${errors}"></span></p>
    <p>异常跟踪信息:<span th:text="${trace}"></span></p>
    <p>错误发生时请求的URL路径:<span th:text="${spanath}"></span></p>
</body>
</html>

结果

image.png
1@ExceptionHandle****注解

@ExceptionHandle 注解可以根据 value 属性声明需要捕获哪些异常,然后声明一个自定义方法, Spring Boot会将产生的异常对象注入到方法中供我们使用,该方法需要返回 ModelAndView 封装异常信息,指定跳转视图。

案例如下:

UserController.java

@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/show")
public String show() {
Integer.parseInt("D");
return "index";
} 
@RequestMapping("/info")
public String info() {
int i = 2 / 0;
return "index";
} /
**
* @ExceptionHandler(value = {}):捕获哪些异常
* ModelAndView:封装异常信息,指定跳转视图
* Exception ex:将产生的异常对象注入到方法中
* @param ex
* @return
*/
@ExceptionHandler(value = {NumberFormatException.class})
public ModelAndView numberFormatExceptionHandler(Exception ex){
ModelAndView mv = new ModelAndView();
mv.addObject("error", ex.toString());
mv.setViewName("numberFormatException");
return mv;
} /
**
* @ExceptionHandler(value = {}):捕获哪些异常
* ModelAndView:封装异常信息,指定跳转视图
* Exception ex:将产生的异常对象注入到方法中
* @param ex
* @return
*/
@ExceptionHandler(value = {ArithmeticException.class})
public ModelAndView arithmeticExceptionHandler(Exception ex){
ModelAndView mv = new ModelAndView();
mv.addObject("error", ex.toString());
mv.setViewName("arithmeticException");
return mv;
}
}

numberFormatException.html

数字转换异常跳转页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>类型转换异常</title>
</head>
<body>
    <span th:text="${error}"></span>
</body>
</html>

arithmeticException.html

运算异常跳转页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>算数运算异常</title>
</head>
<body>
    <span th:text="${error}"></span>
</body>
</html>

结果:

image.png
2.@ControllerAdvice****注解
image.png

@ExceptionHandle 注解单独使用时需要借助自定义方法实现,如果多个类中都会有相同的异常出现,那么就需要声明多次,造成代码冗余,此时我们可以借助 @ControllerAdvice 注解实现代码重复利用

通过 @ControllerAdvice 注解声明一个全局异常处理类,然后配合 @ExceptionHandle 注解实现异常处理。

[图片上传失败...(image-4151fd-1566671305409)]

案例如下:

GlobalException.java

/**
* 全局异常处理类
*/
@ControllerAdvice
public class GlobalException {
/**
* @ExceptionHandler(value = {}):捕获哪些异常
* ModelAndView:封装异常信息,指定跳转视图
* Exception ex:将产生的异常对象注入到方法中
* @param ex
* @return
*/
@ExceptionHandler(value = {NumberFormatException.class})
public ModelAndView numberFormatExceptionHandler(Exception ex){
ModelAndView mv = new ModelAndView();
mv.addObject("error", ex.toString());
mv.setViewName("numberFormatException");
return mv;
} /
**
* @ExceptionHandler(value = {}):捕获哪些异常
* ModelAndView:封装异常信息,指定跳转视图
* Exception ex:将产生的异常对象注入到方法中
* @param ex
* @return
*/
@ExceptionHandler(value = {ArithmeticException.class})
public ModelAndView arithmeticExceptionHandler(Exception ex){
ModelAndView mv = new ModelAndView();
mv.addObject("error", ex.toString());
mv.setViewName("arithmeticException");
return mv;
}
}

numberFormatException.html

arithmeticException.html

将UserController中的捕获异常声明删除

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/show")
    public String show() {
        Integer.parseInt("D");
        return "index";
    }

    @RequestMapping("/info")
    public String info() {
        int i = 2 / 0;
        return "index";
    }

}

结果:

image.png
3.SimpleMappingExceptionResolver

我们可以在全局异常处理类中使用 @Bean 注解注入 SimpleMappingExceptionResolver 实现异常处理。

这种对异常的处理只是定义了异常与视图映射的信息,无法给页面返回太多异常信息。

案例如下:

GlobalException.java

/**
* 全局异常处理类
*/
@Configuration
public class GlobalException {
@Bean
public SimpleMappingExceptionResolver
getSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver resolver = new
SimpleMappingExceptionResolver();
Properties mappings = new Properties();
/**
* 参数一:异常的类型,注意必须是异常类型的全名
* 参数二:视图名称
*/
mappings.put(ArithmeticException.class.getSimpleName(),
"arithmeticException");
mappings.put(NumberFormatException.class.getSimpleName(),
"numberFormatException");
// 设置异常与视图映射信息
resolver.setExceptionMappings(mappings);
return resolver;
}
}

numberFormatException.html

arithmeticException.html

结果:

image.png
4.HandlerExceptionResolver

我们可以在全局异常处理类中实现 HandlerExceptionResolver 接口实现异常处理。

案例如下:

GlobalException.java

/**
 * 全局异常处理类
 */
@Configuration
public class GlobalException implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler, Exception ex) {
        ModelAndView mv = new ModelAndView();
        // 判断异常类型,做不同视图跳转
        if(ex instanceof ArithmeticException){
            mv.setViewName("arithmeticException");
        }
        if(ex instanceof NumberFormatException){
            mv.setViewName("numberFormatException");
        }
        mv.addObject("error", ex.toString());
        return mv;
    }


numberFormatException.html

arithmeticException.html

结果:

image.png
image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容