前言:在日常的开发工作中,项目在运行过程中多多少少是避免不了报错的,对于报错信息肯定不可以把全部信息都抛给客户端去显示,这里就需要我们对常见的七种异常情况统一进行处理,让整个项目更加优雅。
目录
一、基本介绍
二、项目整体结构图
三、基础配置
1、导入pom.xml依赖
2、application.yml配置
四、常用类封装
1、HttpStatus状态码常量类
2、AjaxResult统一封装返回的结果类
3、ServiceException业务异常类封装
4、User实体类
五、数据库查询
1、UserMapper.xml文件
2、Mapper接口
六、ExceptionAdvice核心全局拦截配置类
七、异常测试
1、权限校验异常
2、请求方式异常
3、参数校验异常
4、数据库异常(非常重要)
5、运行异常
6、业务异常
7、全局异常
八、Gitee源码
一、基本介绍
这次博客的主角就是@RestControllerAdvice这个注解,这个一个组合注解由@ControllerAdvice和@ResponseBody组成,@RestControllerAdvice会帮助我们把信息转成json格式返回。
在全局异常处理类只需要在类上标注@RestControllerAdvice,并在处理相应异常的方法上使用@ExceptionHandler注解,写明处理哪个异常即可。
注:异常的拦截有顺序,子类异常会优先匹配子类异常处理器。
废话不多说,本博客列举了实际开发中常见的七种异常进行配置,直接上代码!
二、项目整体结构图
这是项目最后的运行的整个结构
三、基础配置
1、导入pom.xml依赖
项目中引入的依赖包都贴出来了,一共这么多复制即可。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 常用工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- 参数验证依赖 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.5.Final</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.26</version>
</dependency>
<!--Mybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2、application.yml配置
server:
port: 8080
spring:
datasource:
username: 账号
password: 密码
url: jdbc:mysql://地址:3306/数据库?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapping/*.xml
四、常用类封装
1、HttpStatus状态码常量类
这边定义了目前常见的响应的状态码,直接拷贝即可。
package com.example.exception.constant;
/**
* 返回状态码
* @author HTT
*/
public class HttpStatus
{
/**
* 操作成功
*/
public static final int SUCCESS = 200;
/**
* 对象创建成功
*/
public static final int CREATED = 201;
/**
* 请求已经被接受
*/
public static final int ACCEPTED = 202;
/**
* 操作已经执行成功,但是没有返回数据
*/
public static final int NO_CONTENT = 204;
/**
* 资源已被移除
*/
public static final int MOVED_PERM = 301;
/**
* 重定向
*/
public static final int SEE_OTHER = 303;
/**
* 资源没有被修改
*/
public static final int NOT_MODIFIED = 304;
/**
* 参数列表错误(缺少,格式不匹配)
*/
public static final int BAD_REQUEST = 400;
/**
* 未授权
*/
public static final int UNAUTHORIZED = 401;
/**
* 访问受限,授权过期
*/
public static final int FORBIDDEN = 403;
/**
* 资源,服务未找到
*/
public static final int NOT_FOUND = 404;
/**
* 不允许的http方法
*/
public static final int BAD_METHOD = 405;
/**
* 资源冲突,或者资源被锁
*/
public static final int CONFLICT = 409;
/**
* 不支持的数据,媒体类型
*/
public static final int UNSUPPORTED_TYPE = 415;
/**
* 系统内部错误
*/
public static final int ERROR = 500;
/**
* 接口未实现
*/
public static final int NOT_IMPLEMENTED = 501;
}
2、AjaxResult统一封装返回的结果类
package com.example.exception.domain;
import com.example.exception.constant.HttpStatus;
import org.apache.commons.lang3.ObjectUtils;
import java.util.HashMap;
/**
* 操作消息提醒
*
* @author HTT
*/
public class AjaxResult extends HashMap<String, Object>
{
private static final long serialVersionUID = 1L;
/** 状态码 */
public static final String CODE_TAG = "code";
/** 返回内容 */
public static final String MSG_TAG = "msg";
/** 数据对象 */
public static final String DATA_TAG = "data";
/**
* 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
*/
public AjaxResult()
{
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
*/
public AjaxResult(int code, String msg)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
* @param data 数据对象
*/
public AjaxResult(int code, String msg, Object data)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
if (ObjectUtils.isNotEmpty(data))
{
super.put(DATA_TAG, data);
}
}
/**
* 返回成功消息
*
* @return 成功消息
*/
public static AjaxResult success()
{
return AjaxResult.success("操作成功");
}
/**
* 返回成功数据
*
* @return 成功消息
*/
public static AjaxResult success(Object data)
{
return AjaxResult.success("操作成功", data);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @return 成功消息
*/
public static AjaxResult success(String msg)
{
return AjaxResult.success(msg, null);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static AjaxResult success(String msg, Object data)
{
return new AjaxResult(HttpStatus.SUCCESS, msg, data);
}
/**
* 返回错误消息
*
* @return
*/
public static AjaxResult error()
{
return AjaxResult.error("操作失败");
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult error(String msg)
{
return AjaxResult.error(msg, null);
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static AjaxResult error(String msg, Object data)
{
return new AjaxResult(HttpStatus.ERROR, msg, data);
}
/**
* 返回错误消息
*
* @param code 状态码
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult error(int code, String msg)
{
return new AjaxResult(code, msg, null);
}
/**
* 方便链式调用
*
* @param key 键
* @param value 值
* @return 数据对象
*/
@Override
public AjaxResult put(String key, Object value)
{
super.put(key, value);
return this;
}
}
3、ServiceException业务异常类封装
package com.example.exception.exception;
/**
* 业务异常类封装
* @author HTT
*/
public final class ServiceException extends RuntimeException
{
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 错误明细,内部调试错误
*/
private String detailMessage;
/**
* 空构造方法,避免反序列化问题
*/
public ServiceException()
{
}
public ServiceException(String message)
{
this.message = message;
}
public ServiceException(String message, Integer code)
{
this.message = message;
this.code = code;
}
public String getDetailMessage()
{
return detailMessage;
}
@Override
public String getMessage()
{
return message;
}
public Integer getCode()
{
return code;
}
public ServiceException setMessage(String message)
{
this.message = message;
return this;
}
public ServiceException setDetailMessage(String detailMessage)
{
this.detailMessage = detailMessage;
return this;
}
}
4、User实体类
package com.example.exception.domain;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class User {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
}
五、数据库查询
1、UserMapper.xml文件
这边我故意查询的是我数据库中目前不存在的表none_txt
<?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">
<mapper namespace="com.example.exception.mapper.UserMapper">
<select id="select" resultType="Integer">
SELECT * FROM none_txt
</select>
</mapper>
2、Mapper接口
package com.example.exception.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
public void select();
}
六、ExceptionAdvice核心全局拦截配置类
这边我一共列举了实际项目开发当中常见的七种异常情况:
1、权限校验异常
@ExceptionHandler(AccessDeniedException.class)
public AjaxResult handleAccessDeniedException(AccessDeniedException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址{},权限校验失败{}", requestURI, e.getMessage());
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
}
2、请求方式不支持
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址{},不支持{}请求", requestURI, e.getMethod());
return AjaxResult.error(e.getMessage());
}
3、参数验证失败异常
public AjaxResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
String message = e.getBindingResult().getFieldError().getDefaultMessage();
log.error("请求地址{},参数验证失败{}", requestURI, e.getObjectName(),e);
return AjaxResult.error(message);
}
4、数据库异常
错误SQL语句异常
@ExceptionHandler(BadSqlGrammarException.class)
public AjaxResult handleBadSqlGrammarException(BadSqlGrammarException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生数据库异常.", requestURI, e);
return AjaxResult.error(HttpStatus.ERROR, "数据库异常!");
}
拦截表示违反数据库的完整性约束导致的异常
@ExceptionHandler(DataIntegrityViolationException.class)
public AjaxResult handleDataIntegrityViolationException(DataIntegrityViolationException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生数据库异常.", requestURI, e);
return AjaxResult.error(HttpStatus.ERROR, "数据库异常!");
}
拦截违反数据库的非完整性约束导致的异常,可能也会拦截一些也包括 SQL 语句错误、连接问题、权限问题等各种数据库异常。
@ExceptionHandler(UncategorizedSQLException.class)
public AjaxResult handleUncategorizedSqlException(UncategorizedSQLException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生数据库异常.", requestURI, e);
return AjaxResult.error(HttpStatus.ERROR, "数据库异常!");
}
5、拦截未知的运行时异常
@ExceptionHandler(RuntimeException.class)
public AjaxResult handleRuntimeException(RuntimeException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址{},发生未知运行异常", requestURI, e);
return AjaxResult.error(e.getMessage());
}
6、业务自定义异常
@ExceptionHandler(ServiceException.class)
public AjaxResult handleServiceException(ServiceException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
Integer code = e.getCode();
log.error("请求地址{},发生业务自定义异常{}",requestURI,e.getMessage(), e);
return code != null ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
}
7、全局异常
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e,
HttpServletRequest request){
String requestURI = request.getRequestURI();
log.error("请求地址{},发生系统异常",requestURI,e);
return AjaxResult.error(e.getMessage());
}
完整代码:
package com.example.exception.exception;
import com.example.exception.constant.HttpStatus;
import com.example.exception.domain.AjaxResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.nio.file.AccessDeniedException;
/**
* @author HTT
*/
@RestControllerAdvice
@Slf4j
public class ExceptionAdvice {
/**
* 权限校验异常
* @param e
* @param request
* @return
*/
@ExceptionHandler(AccessDeniedException.class)
public AjaxResult handleAccessDeniedException(AccessDeniedException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址{},权限校验失败{}", requestURI, e.getMessage());
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
}
/**
* 请求方式不支持
* @param e
* @param request
* @return
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址{},不支持{}请求", requestURI, e.getMethod());
return AjaxResult.error(e.getMessage());
}
/**
* 参数验证失败异常
* @param e
* @param request
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public AjaxResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
String message = e.getBindingResult().getFieldError().getDefaultMessage();
log.error("请求地址{},参数验证失败{}", requestURI, e.getObjectName(),e);
return AjaxResult.error(message);
}
/**
* 拦截错误SQL异常
* @param e
* @param request
* @return
*/
@ExceptionHandler(BadSqlGrammarException.class)
public AjaxResult handleBadSqlGrammarException(BadSqlGrammarException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生数据库异常.", requestURI, e);
return AjaxResult.error(HttpStatus.ERROR, "数据库异常!");
}
/**
* 可以拦截表示违反数据库的完整性约束导致的异常。
* @param e
* @param request
* @return
*/
@ExceptionHandler(DataIntegrityViolationException.class)
public AjaxResult handleDataIntegrityViolationException(DataIntegrityViolationException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生数据库异常.", requestURI, e);
return AjaxResult.error(HttpStatus.ERROR, "数据库异常!");
}
/**
* 可以拦截违反数据库的非完整性约束导致的异常,可能也会拦截一些也包括 SQL 语句错误、连接问题、权限问题等各种数据库异常。
* @param e
* @param request
* @return
*/
@ExceptionHandler(UncategorizedSQLException.class)
public AjaxResult handleUncategorizedSqlException(UncategorizedSQLException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生数据库异常.", requestURI, e);
return AjaxResult.error(HttpStatus.ERROR, "数据库异常!");
}
/**
* 拦截未知的运行时异常
* @param e
* @param request
* @return
*/
@ExceptionHandler(RuntimeException.class)
public AjaxResult handleRuntimeException(RuntimeException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址{},发生未知运行异常", requestURI, e);
return AjaxResult.error(e.getMessage());
}
/**
* 业务自定义异常
* @param e
* @param request
* @return
*/
@ExceptionHandler(ServiceException.class)
public AjaxResult handleServiceException(ServiceException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
Integer code = e.getCode();
log.error("请求地址{},发生业务自定义异常{}",requestURI,e.getMessage(), e);
return code != null ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
}
/**
* 全局异常
* @param e
* @param request
* @return
*/
@ExceptionHandler(Exception.class)
public AjaxResult handleException(Exception e,
HttpServletRequest request){
String requestURI = request.getRequestURI();
log.error("请求地址{},发生系统异常",requestURI,e);
return AjaxResult.error(e.getMessage());
}
}
七、异常测试
这边我们逐一对每一个异常进行拦截测试。
1、权限校验异常
测试运行代码:
/**
* 权限测试
*/
@GetMapping("/auth")
public void auth() throws AccessDeniedException {
throw new AccessDeniedException("暂无权限");
}
浏览器输入:http://localhost:8080/user/auth
浏览器显示如下:
后台日志显示如下:
2、请求方式异常
测试运行代码:
/**
* 请求不支持异常测试
*/
@PostMapping("/request")
public void request() {
System.out.println("request");
}
浏览器输入:http://localhost:8080/user/request
浏览器显示如下:
后台日志显示如下:
3、参数校验异常
测试运行代码:
/**
* 参数验证异常测试
* @param user
*/
@PostMapping("/valid")
public void Valid(@Valid @RequestBody User user){
System.out.println(user.toString());
}
使用postman测试:
后台日志显示如下:
4、数据库异常(非常重要)
这边我们就拿BadSqlGrammarException这个异常进行举例,如果表、字段或者视图等情况不存在会抛出BadSqlGrammarException异常,而这个异常是继承RuntimeException异常的,进行了特殊处理,不然会把SQL语句也暴漏给客户端显示。
测试代码如下:
@Resource
private UserMapper userMapper;
/**
* 数据库异常
*/
@GetMapping("/badSqlGrammarException")
public void badSqlGrammarException() throws Exception {
userMapper.select();
}
如果把这段代码注释掉:
/**
* 拦截错误SQL异常
* @param e
* @param request
* @return
*/
@ExceptionHandler(BadSqlGrammarException.class)
public AjaxResult handleBadSqlGrammarException(BadSqlGrammarException e, HttpServletRequest request)
{
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生SQL异常.", requestURI, e);
return AjaxResult.error(HttpStatus.ERROR, "数据库异常!");
}
浏览器输入:http://localhost:8080/user/badSqlGrammarException
浏览器显示如下:
显而易见,当我们注释掉了这段自定义的异常以后,返回给前端的错误信息中居然还包含了我们的SQL执行语句,这显然是不合理的,应该是浏览器端只能提示一个例如数据库异常的通用提示,具体信息在我们的后台进行记录。
所以当我们加上那段自定义异常,再执行一遍看效果:
后台日志显示如下:
注意:这个异常属于BadSqlGrammarException,它是SpringFramework定义的异常,而SQLException属于Java SQL标准定义的异常。如果要被异常拦截器拦截的话,定义的异常应该是BadSqlGrammarException.class进行捕获。
BadSqlGrammarException和SQLException属于并列的异常,BadSqlGrammarException并未继承SQLException。但它们都属于同一个父类RuntimeException
另外还有2个可能会发生的异常
1、DataIntegrityViolationException:可以拦截表示违反数据库的完整性约束导致的异常。
2、UncategorizedSQLException:可以拦截违反数据库的非完整性约束导致的异常,可能也会拦截一些也包括 SQL 语句错误、连接问题、权限问题等各种数据库异常。
完整性约束:
1、主键约束:唯一标识表中每条记录的约束。
2、非空约束:要求字段的值不能为空的约束。
3、唯一约束:要求字段的值在表中唯一的约束。
4、外键约束:要求字段的值必须是另一个表的主键约束的值的约束。
非完整性约束:
1、数据类型约束:对字段的数据类型、长度、格式等做出的限制。
如字段长度限制、数字位数限制、日期格式限制等。
2、默认值约束:对字段的默认值做出的规定。
如字段的默认值,如果插入数据时未指定该字段的值,则使用默认值。
3、检查约束:对字段的值做更加灵活的约束。
如值的范围、值之间的关系等,属于逻辑约束。
总结来说:非完整性约束主要包括数据类型约束、默认值约束和检查约束。它们分别定义了字段的数据类型、默认值以及更为复杂的逻辑限制。而完整性约束则涉及实体完整性,如主键、非空、唯一以及外键约束。
5、运行异常
测试运行代码:
/**
* 运行异常
*/
@GetMapping("/runtimeException")
public void runtimeException(){
throw new RuntimeException();
}
浏览器输入:http://localhost:8080/user/request
浏览器显示如下:
后台日志显示如下:
6、业务异常
测试运行代码:
/**
* 业务自定义异常
*/
@GetMapping("/serviceException")
public void serviceException() {
throw new ServiceException("业务异常!");
}
浏览器输入:http://localhost:8080/user/request
浏览器显示如下:
后台日志显示如下:
7、全局异常
测试运行代码:
/**
* 全局异常
*/
@GetMapping("/exception")
public void exception() throws Exception {
throw new Exception("全局异常!");
}
浏览器输入:http://localhost:8080/user/exception
浏览器显示如下:
后台日志显示如下:
八、Gitee源码
SpringBoot处理实际开发中常见的七种全局异常详解: 在日常的开发工作中,项目在运行过程中多多少少是避免不了报错的,对于报错信息肯定不可以把全部信息都抛给客户端去显示,这里就需要我们对常见的七种异常情况统一进行处理,让整个项目更加优雅。