SpringBoot 中异常处理与单元测试
1. 异常处理
SpringBoot 中对于异常处理提供了五种处理方式
package com.sxt.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class UserController {
@RequestMapping("showInfo")
public String showInfo(){
String str = null;
System.out.println(str.length());
return "success";
}
}
1.1自定义错误页面
SpringBoot 默认的处理异常的机制:SpringBoot默认的已经提供了一套处理异常的机制。一旦程序中出现了异常 SpringBoot 会向/error 的 url 发送请求。在 SpringBoot 中提供了一个名为 BasicErrorController 来处理/error 请求,然后跳转到默认显示异常的页面来展示异常信息。
如 果 我 们 需 要 将 所 有 的 异 常 同 一 跳 转 到 自 定 义 的 错 误 页 面 , 需 要 在 src/main/resources/templates 目录下创建 error.html 页面。注意:页面名称必须叫 error
1.2 通过@ExceptionHandler 注解处理异常
package com.sxt.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class UserController {
@RequestMapping("showInfo")
public String showInfo(){
String str = null;
System.out.println(str.length());
return "success";
}
@ExceptionHandler(value = {java.lang.NullPointerException.class})
public ModelAndView exception01(Exception e){
ModelAndView mv = new ModelAndView();
mv.addObject("errorMessage1",e.toString());
mv.setViewName("error01");
return mv;
}
}
创建页面(error01.html):
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>错误处信息页面</title>
</head>
<body>
出错了......<br/>
错误信息:
<span th:text="${errorMessage1}"/>
</body>
</html>
1.3 通过@ControllerAdvice 与@ExceptionHandler 注解处理异常
1.3.1创建全局异常处理类
package com.sxt.exception;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
/**
* 全局异常处理类:
* 缺点:当异常过多时,方法的书写量大
*/
//@ControllerAdvice
public class GlobalException {
@ExceptionHandler(value = {java.lang.NullPointerException.class})
public ModelAndView exception01(Exception e){
ModelAndView mv = new ModelAndView();
mv.addObject("errorMessage1",e.toString());
mv.setViewName("error01");
return mv;
}
@ExceptionHandler(value = {java.lang.ArithmeticException.class})
public ModelAndView exception02(Exception e){
ModelAndView mv = new ModelAndView();
mv.addObject("errorMessage2",e.toString());
mv.setViewName("error02");
return mv;
}
}
1.4 通过 SimpleMappingExceptionResolver 对象处理异常
1.4.1 创建全局异常处理类
package com.sxt.exception;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import java.util.Properties;
/**
* 全局异常处理类:
* 通过 SimpleMappingExceptionResolver 对象处理异常
* 缺点:不能够传递异常信息
*/
//@Configuration
public class GlobalException2 {
@Bean
public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
/**
* 参数一:异常类型,并且是全名
* 参数二:跳转的视图名称
*/
properties.setProperty("java.lang.NullPointerException","error03");
properties.setProperty("java.lang.ArithmeticException","error04");
resolver.setExceptionMappings(properties);
return resolver;
}
}
1.5 通过自定义 HandlerExceptionResolver 对象处理异常 (常用)
1.5.1 创建全局异常处理类
package com.sxt.exception;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 全局异常处理类:
* 通过自定义 HandlerExceptionResolver 对象处理异常
* 必须要实现 HandlerExceptionResolver
*/
@Configuration
public class GlobalException3 implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView mv = new ModelAndView();
// 判断不同异常类型,做不同视图的跳转
if (ex instanceof NullPointerException) {
mv.setViewName("error05");
}
if (ex instanceof ArithmeticException) {
mv.setViewName("error06");
}
mv.addObject("error",ex.toString());
return mv;
}
}
2. Spring Boot 整合 Junit 单元测试
SpringBoot2.0之前使用Junit3/4进行测试
SpringBoot2.x 使用 Junit5 作为测试平台
2.1 修改 POM 文件添加 Test 启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
Spring Boot 服务端数据校验
1、 Spring Boot 对实体对象的校验
1.1 搭建项目环境
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sxt</groupId>
<artifactId>1126-02-springboot-datacheck</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>1126-02-springboot-datacheck</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.1.1 创建实体(get、set方法):
public class Users {
private Integer userid;
private String username;
private String usersex;
}
1.1.2 创建controller方法:
@RequestMapping("/user")
public class UserController {
/**
* 添加用户
*/
@RequestMapping("/addUser")
public String addUser(Users users){
System.out.println(users);
return "success";
}
}
1.1.3 创建页面:
addUser.html:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:th="http://www.thymeleaf.org">
<link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/>
<title>添加用户</title>
</head>
<body>
<form th:action="@{/user/addUser}" method="post">
用户名称:<input type="text" name="username" />
用户性别:<input type="text" name="usersex" />
<input type="submit" value="添加" />
</form>
</body>
</html>
success.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>处理请求成功页面</title>
</head>
<body>
请求成功.....
</body>
</html>
1.2 对实体对象做数据校验
1.2.1Spring Boot 数据校验的技术特点
Spring Boot 中使用了 Hibernate-validator 校验框架。
1.2.2对实体对象数据校验步骤
1.2.2.1 修改实体类添加校验规则
package com.sxt.pojo;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* @NotNull: 对基本数据类型的对象类型做非空校验
* @NotBlank: 对字符串类型做非空校验
* @NotEmpty: 对集合类型做非空校验
*/
public class Users {
@NotNull
private Integer userid;
@NotBlank
private String username;
@NotBlank
private String usersex;
@Override
public String toString() {
return "Users{" +
"userid=" + userid +
", username='" + username + ''' +
", usersex='" + usersex + ''' +
'}';
}
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUsersex() {
return usersex;
}
public void setUsersex(String usersex) {
this.usersex = usersex;
}
}
1.2.2.2 在 Controller 中开启校验(@Validated)
package com.sxt.controller;
import com.sxt.pojo.Users;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@RequestMapping("/user")
public class UserController {
/**
* 添加用户
*/
@RequestMapping("/addUser")
public String addUser(@Validated Users users, BindingResult result){
if (result.hasErrors()) {
List<ObjectError> list = result.getAllErrors();
for (Object err : list) {
FieldError fieldError = (FieldError)err;
String errName = fieldError.getField();
String message = fieldError.getDefaultMessage();
System.out.println(errName+" "+message);
}
return "addUser";
}
System.out.println(users);
return "success";
}
}
1.2.2.3 在页面中获取提示信息
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:th="http://www.thymeleaf.org">
<link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/>
<title>添加用户</title>
</head>
<body>
<form th:action="@{/user/addUser}" method="post">
用户名称:<input type="text" name="username" />
<font color="red"><span th:errors="${users.username}"></span></font><br/>
用户性别:<input type="text" name="usersex" />
<font color="red"><span th:errors="${users.usersex}"></span></font><br/>
<input type="submit" value="添加" />
</form>
</body>
</html>
1.2.3自定义错误提示信息 (两种方式)
1.2.3.1 在注解中定义提示信息 (硬编码,不方便修改)
package com.sxt.pojo;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* @NotNull: 对基本数据类型的对象类型做非空校验
* @NotBlank: 对字符串类型做非空校验
* @NotEmpty: 对集合类型做非空校验
*/
public class Users {
@NotNull(message = "用户ID不能为空")
private Integer userid;
@NotBlank(message = "用户名称不能为空")
private String username;
@NotBlank(message = "用户性别不能为空")
private String usersex;
@Override
public String toString() {
return "Users{" +
"userid=" + userid +
", username='" + username + ''' +
", usersex='" + usersex + ''' +
'}';
}
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUsersex() {
return usersex;
}
public void setUsersex(String usersex) {
this.usersex = usersex;
}
}
1.2.3.2 在配置文件中定义提示信息(resources目录下 便于修改)
配置文件名必须是 ValidationMessages.properties
userid.notnull=用户ID不能为空
username.notnull=用户名称不能为空
usersex.notnull=用户性别不能为空
在实体类中引入:
package com.sxt.pojo;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* @NotNull: 对基本数据类型的对象类型做非空校验
* @NotBlank: 对字符串类型做非空校验
* @NotEmpty: 对集合类型做非空校验
*/
public class Users {
@NotNull(message = "{userid.notnull}")
private Integer userid;
@NotBlank(message = "{username.notnull}")
private String username;
@NotBlank(message = "{usersex.notnull}")
private String usersex;
@Override
public String toString() {
return "Users{" +
"userid=" + userid +
", username='" + username + ''' +
", usersex='" + usersex + ''' +
'}';
}
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUsersex() {
return usersex;
}
public void setUsersex(String usersex) {
this.usersex = usersex;
}
}
出现乱码问题,解决方法:
原因:配置文件的编码集是使用ISO-8859-1进行编码
转码方式:使用jdk自带的native2ascii.exe转换为对应的字节文件
使用方法:
C:UsersLenovo>cd "C:Program FilesJavajdk1.8.0_91bin
C:Program FilesJavajdk1.8.0_91bin>native2ascii.exe E:/aa.txt E:/bb.txt
userid.notnull=u7528u6237Idu4e0du80fdu4e3au7a7a
username.notnull=u7528u6237u59d3u540du4e0du80fdu4e3au7a7a
usersex.notnull=u7528u6237u6027u522bu4e0du80fdu4e3au7a7a
1.2.4解决页面页面的controller:
package com.sxt.controller;
import com.sxt.pojo.Users;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class PageController {
@RequestMapping("/{page}")
public String showPage(@PathVariable String page){
return page;
}
}
解决方法:
在跳转页面的方法中注入一个对象,要求参数对象的变量名必须是对象类型名称首字母小写格式。
package com.sxt.controller;
import com.sxt.pojo.Users;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 跳转页面方法
* 解决异常的方式:可以在跳转页面的方法中注入一个 Users 对象
* 由于 SprignMVC 会将该对象放入到 Model 中传递,key 的名称会使用该对象的驼峰命名规则来作为 key
*/
@Controller
public class PageController {
@RequestMapping("/{page}")
public String showPage(@PathVariable String page, Users users){
return page;
}
}
1.2.5 修改参数 key 的名称 (使用@ModelAttribute)
PageController:
package com.sxt.controller;
import com.sxt.pojo.Users;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* 跳转页面方法
* 解决异常的方式:可以在跳转页面的方法中注入一个 Users 对象
* 由于 SprignMVC 会将该对象放入到 Model 中传递,key 的名称会使用该对象的驼峰命名规则来作为 key
*/
@Controller
public class PageController {
@RequestMapping("/{page}")
public String showPage(@PathVariable String page,@ModelAttribute("aa") Users users){
return page;
}
}
UserController:
package com.sxt.controller;
import com.sxt.pojo.Users;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@RequestMapping("/user")
public class UserController {
/**
* 添加用户
*/
@RequestMapping("/addUser")
public String addUser(@ModelAttribute("aa") @Validated Users users, BindingResult result){
if (result.hasErrors()) {
List<ObjectError> list = result.getAllErrors();
for (Object err : list) {
FieldError fieldError = (FieldError)err;
String errName = fieldError.getField();
String message = fieldError.getDefaultMessage();
System.out.println(errName+" "+message);
}
return "addUser";
}
System.out.println(users);
return "success";
}
}
addUser.html:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:th="http://www.thymeleaf.org">
<link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/>
<title>添加用户</title>
</head>
<body>
<form th:action="@{/user/addUser}" method="post">
用户名称:<input type="text" name="username" />
<font color="red"><span th:errors="${aa.username}"></span></font><br/>
用户性别:<input type="text" name="usersex" />
<font color="red"><span th:errors="${aa.usersex}"></span></font><br/>
<input type="submit" value="添加" />
</form>
</body>
</html>
1.2.6 其他校验规则
@NotNull: 判断基本数据类型的对象类型是否为 null
@NotBlank: 判断字符串是否为 null 或者是空串(去掉首尾空格)。
@NotEmpty: 判断集合是否为空。
@Length: 判断字符的长度(最大或者最小)
@Min: 判断数值最小值
@Max: 判断数值最大值
@Email: 判断邮箱是否合法
2. Spring Boot 对 Controller 中其他参数的校验
2.1 编写页面 (findUser.html)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:th="http://www.thymeleaf.org">
<link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/>
<title>添加用户</title>
</head>
<body>
<form th:action="@{/user/findUser}" method="post">
用户名称:<input type="text" name="username" />
<input type="submit" value="查询" />
</form>
</body>
</html>
请求方法:
@PostMapping("/findUser")
public String findUser(@NotBlank String username){
System.out.println(username);
return "success";
}
点击查询后:
2.2 对参数指定校验规则 ,并在 Controller 中开启校验
package com.sxt.controller;
import com.sxt.pojo.Users;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.validation.constraints.NotBlank;
import java.util.List;
@Controller
@RequestMapping("/user")
@Validated
public class UserController {
/**
* 添加用户
*/
@RequestMapping("/addUser")
public String addUser( @Validated Users users, BindingResult result){
// public String addUser(@ModelAttribute("aa") @Validated Users users, BindingResult result){
if (result.hasErrors()) {
List<ObjectError> list = result.getAllErrors();
for (Object err : list) {
FieldError fieldError = (FieldError)err;
String errName = fieldError.getField();
String message = fieldError.getDefaultMessage();
System.out.println(errName+" "+message);
}
return "addUser";
}
System.out.println(users);
return "success";
}
@PostMapping("/findUser")
public String findUser(@NotBlank String username){
System.out.println(username);
return "success";
}
}
点击查询后:
2.3 通过全局异常处理来跳转页面
package com.sxt.exception;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
@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 NullPointerException) {
mv.setViewName("error05");
}
if (ex instanceof ArithmeticException) {
mv.setViewName("error06");
}
if(ex instanceof ConstraintViolationException) {
mv.setViewName("findUser");
}
mv.addObject("error",ex.getMessage().split(":")[1]);
return mv;
}
}
findUser.html页面(接收信息):
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns:th="http://www.thymeleaf.org">
<link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/>
<title>添加用户</title>
</head>
<body>
<form th:action="@{/user/findUser}" method="post">
用户名称:<input type="text" name="username" />
<span style="color: red" th:text="${error}"/><br/>
<input type="submit" value="查询" />
</form>
</body>
</html>
点击查询后:
可设置错误提示信息:
Spring Boot 热部署
1 通过 DevTools 工具实现热部署
1.1修改 POM 文件,添加 DevTools 依赖
<!-- 热部署依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
1.2配置 Idea
1.2.1设置自动编译 (Ctrl+Alt+s)
1.2.2设置 Idea 的 Registry
通过快捷键打开该设置项:Ctrl+Shift+Alt+/
勾选 complier.automake.allow.when.app.running