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);
}
}
结果
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);
}
}
结果
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);
}
}
运行结果出现异常(如果是 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>
运行结果
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
创建项目
创建 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);
}
}
结果
页面请求
数据库
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>
结果
查询所有用户
根据主键查询用户
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>
结果
查询用户,并填写修改信息
数据库
1.4删除用户
结果
查询用户列表
数据库
Spring Boot****服务端数据校验
项目中使用较多的是前端的校验,比如页面中js校验。对于安全较高的建议在服务端进行校验。
校验框架
Spring 使用 Hibernate 的校验框架 validator,页面提交的请求参数,请求到 controller 方法中,使用 validator 进行校验,如果校验不通过,将错误信息展示到页面。
主要使用到以下相关 jar 包(蓝色部分)
1.数据校验
创建项目
使用 IntelliJ IDEA 2019.1 直接创建Spring Boot项目
目前最新的稳定版 Spring Boot 是2.1.7,我们课程中使用的2.1.6,所以需要修改一下pom.xml的版本信息。在创建项目时勾选 Web 和 Themeleaf 组件。
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
准备好项目后,启动项目测试无误,准备开始数据校验.
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>
运行结果
自定义校验提示信息
我们可以通过 message 属性来自定义校验提示信息。
结果
3.分组校验
当需要不同的方法对同一个对象进行校验,但是每个方法需要不同的校验时,需要将校验规则分组。
定义校验分组
ValidateGroupForName.java
package com.springboot.group;
public interface ValidateGroupForName {
// 接口中不需要定义任何方法,仅是对不同的校验规则进行分组
// 此分组只校验姓名
}
ValidateGroupForAll.java
package com.springboot.group;
public interface ValidateGroupForAll {
// 接口中不需要定义任何方法,仅是对不同的校验规则进行分组
// 此分组校验全部
}
User.java
通过注解配置添加分组
每个方法使用不同的校验分组
此时,添加用户方法根据 ValidateGroupForName 分组校验,只会校验用户名,结果如下。
Spring Boot****异常处理
Spring Boot对于异常处理提供了五种处理方式。
创建Spring Boot项目,选择 Web 和 Thymeleaf 组件。
1.默认
[图片上传失败...(image-d5c852-1566671305409)]
Spring Boot默认的处理异常的机制是:一旦程序中出现了异常Spring Boot会向 /error 的 url 发送请求。在Spring Boot中提供了一个叫 BasicExceptionController 来处理 /error 请求,然后跳转到默认显示异常的页面来展示异常信息。
如果我们需要将所有的异常统一跳转到错误页面显式,需要在 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>
结果
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>
结果:
2.@ControllerAdvice****注解
@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";
}
}
结果:
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
结果:
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
结果: