业务需求背景:
需求说明:为了不在controller编写大量的try-catch代码,需要进行统一异常处理,同时要进行错误信息以及错误码的统一管理,建议使用枚举进行错误码封装。同时要求系统支持JSR303校验规则。
统一异常处理的思路:
- 创建一个全局的异常处理器(Global Exception Handler):定义一个全局的异常处理类(异常处理器),用于捕获和处理所有未被捕获的异常。
- 定义异常处理方法:在全局异常处理器中定义异常处理方法,用于处理不同类型的异常。可以根据异常的类型、错误代码、错误信息等来进行分类处理,这里用的是通过异常类型来分类,@ExceptionHandler注解标识这个方法处理哪个类型的异常:
- 注册全局异常处理器:将全局异常处理器注册到应用程序中,使其成为默认的异常处理机制。具体的注册方式取决于应用程序的架构和框架,可以是通过配置文件、注解或代码等方式进行注册,这个地方是用的注解的方式:@RestControllerAdvice复合注解中有一个**@ControllerAdvice注解**将控制所有的Controller层并拦截所有的异常
- 异常处理逻辑:这里处理的逻辑是将错误码信息封装到枚举类中,将对应的异常的枚举类中的错误码信息给到自定义的异常中,再封装到AjaxResult响应结果中,响应到客户端
代码:
异常类:
两个成员:
code: 错误码
globalMessage: 异常信息
注意:
使用枚举可以快速的进行查找和设计了
package com.noting.basic.exception;
import lombok.Data;
/**
* 自定义全局异常类GlobalException
*/
@Data
public class GlobalException extends RuntimeException{
private String code;
private String globalMessage;
public GlobalException() {
}
public GlobalException(String message) {
super(message);
}
public GlobalException(String code, String message) {
super(message);
this.code = code;
this.globalMessage = message;
}
// 建立直接传入枚举做参数可以直接创建对应的异常
public GlobalException(GlobalExceptionEnum globalExceptionEnum) {
super(globalExceptionEnum.getMessage());
this.code = globalExceptionEnum.getCode();
this.globalMessage = globalExceptionEnum.getMessage();
}
}
枚举类封装错误码信息:
package com.noting.basic.exception;
import lombok.Getter;
/**
* 枚举类封装错误码信息:
*/
@Getter
public enum GlobalExceptionEnum {
// 1. 字段(枚举实例)
ERROR("-1", "系统异常,请稍后再试!"),
SUCCESS("0", "操作成功!"),
PHONE_IS_NULL_ERROR ("1001", "电话不能为空"),
PARAM_ERROR ("1002", "参数校验异常"),
DELETE_ERROR ("1101", "删除错误"),
UPLOAD_ERROR("1102", "上传文件失败错误"),
;
// 2. 枚举实例
private String code; // 错误码
private String message; // 错误码对应的错误信息提示语
// 3. 字段的构造方法
GlobalExceptionEnum(String code, String message) {
this.code = code;
this.message = message;
}
}
全局异常的处理类:
- 全局异常的处理类**@RestControllerAdvice**
*** **@RestControllerAdvice 该注解是一个复合注解包括下面两个注解- **@ControllerAdvice ** 将控制所有的Controller层并拦截所有的异常
-
@ResponseBody 将本类中所有方法的返回值转换为JSON
相当于每次抛出异常就进入这个地方操作
注意点:
异常的类型不同, 需求不同可以继续添加
package com.noting.basic.exception;
import com.noting.basic.utils.AjaxResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.stream.Collectors;
/**
* 全局异常的处理器@RestControllerAdvice
* @RestControllerAdvice 该注解是一个复合注解包括下面两个注解
* @ControllerAdvice 将控制所有的Controller层并拦截所有的异常
* @ResponseBody 将本类中所有方法的返回值转换为JSON
*/
@RestControllerAdvice // 必须必须要被启动类扫描
public class GlobalExceptionHandler {
/**
* 处理系统异常
* @return
*
* catch (Exception e) {
* e.printStackTrace();
* return AjaxResult.me().setSuccess(false).setMessage("系统繁忙,请重试!");
* }
*/
@ExceptionHandler(Exception.class) //这句代码可以认为是trycatch中的catch
public AjaxResult exceptionHandler(Exception e){
e.printStackTrace();
return AjaxResult.error(GlobalExceptionEnum.ERROR.getCode(), GlobalExceptionEnum.ERROR.getMessage());
}
/**
* 处理自定义业务异常
* @return
*
* catch (GlobalException e) {
* e.printStackTrace();
* return AjaxResult.me().setSuccess(false).setMessage("系统繁忙,请重试!");
* }
*/
@ExceptionHandler(GlobalException.class) //这句代码可以认为是trycatch中的catch
public AjaxResult globalExceptionHandler(GlobalException e){
e.printStackTrace();
return AjaxResult.error(e.getCode(), e.getGlobalMessage());
}
/**
* 处理校验对象时候的异常MethodArgumentNotValidException
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public AjaxResult globalExceptionHandler(MethodArgumentNotValidException e){
e.printStackTrace();
// ((BeanPropertyBindingResult) e.bindingResult).errors.get(0).defaultMessage
ListObjectError> errors = e.getBindingResult().getAllErrors();
// map():对于流的一些中间业务操作就用此方法
// ObjectError::getDefaultMessage:我只要流中对象的defaultMessage
//errors.stream().map(o->o.getDefaultMessage())
// Collectors.joining(","):joining只对字符串生效,用其他类型会报错,把所有的得到的字符串使用逗号进行隔开并组合成一个字符串
String messages = errors.stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(","));
return AjaxResult.error(GlobalExceptionEnum.PARAM_ERROR.getCode(), messages);
}
/**
* 处理校验接口参数时候的异常ConstraintViolationException
* @param e
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
public AjaxResult globalExceptionHandler(ConstraintViolationException e){
e.printStackTrace();
return AjaxResult.error(GlobalExceptionEnum.PARAM_ERROR.getCode(), e.getMessage());
}
}
支持JSR303校验规则实体类改造:
package com.noting.org.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* 员工实体类employee
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
@NotNull(message = "id不能为空")
private Long id;
// 员工名称
@NotBlank(message = "名称不能为空!")
private String username;
// 电话
@NotBlank(message = "电话不能为空!")
private String phone;
// 邮箱
@NotBlank(message = "邮箱不能为空!")
@Email(message = "邮箱格式不正确!")
private String email;
// 密码盐值
private String salt;
// 密码
private String password;
// 年龄
private Integer age;
// 员工状态0:禁用,1:启用
private Integer state;
// 所属部门id
private Long departmentId;
// 登录信息id
private Long logininfoId;
// 店铺id
private Long shopId;
}
全局异常处理类添加方法(校验相关异常):
如果没有定义明确的异常方法, 默认就去到了exception类型这边,
处理校验对象时候的异常MethodArgumentNotValidException
处理校验接口参数时候的异常ConstraintViolationException
一些测试的接口:
//批量删除接口
@ApiOperation(value = "批量删除接口")
@PatchMapping
public AjaxResult patchDel(@RequestBody ListLong> ids){
if (ids == null || ids.isEmpty()){
throw new GlobalException(GlobalExceptionEnum.DELETE_ERROR);
}
departmentService.patchDelete(ids);
return AjaxResult.success();
}
/**
* 查询部门树数据接口
* @return
*/
@ApiOperation(value = "查询部门树数据接口")
@GetMapping("/tree")
public ListDepartment> tree(){
return departmentService.tree();
}
/**
* 测试全局异常
* @return
*/
@GetMapping("/test/{id}")
public AjaxResult test(@PathVariable("id") Long id){
// int a = 1/0;
if(id == 0) throw new GlobalException(GlobalExceptionEnum.PARAM_ERROR);
if(id == 1) throw new GlobalException(GlobalExceptionEnum.DELETE_ERROR);
return AjaxResult.success();
}
@PostMapping("/test")
public AjaxResult test(@Valid @RequestBody Employee employee){
// int a = 1/0;
return AjaxResult.success();
}
@PostMapping("/test2/{age}")
public AjaxResult test2(@Min(value = 18, message = "年龄不能小于18岁!") @PathVariable("age")Integer age) {
return AjaxResult.success();
}
// localhost:8080/department/test2?age = xxxx, 下面这个方法不是restful风格,是普通地址传参
@GetMapping("/test2")
public AjaxResult test3(@Min(value = 18, message = "年龄不能小于18岁!") Integer age) {
return AjaxResult.success();
}
AjaxResult响应结果类的改造:
- 补充了状态码
- success和error的重载方法可以快速创建成功或者失败的结果类,定义了一些传入状态码和消息的传入方式
package com.noting.basic.utils;
import com.noting.basic.exception.GlobalExceptionEnum;
import lombok.Data;
/**
* 返回响应结果类AjaxResult
*/
@Data
public class AjaxResult {
// 默认成功
private boolean success = true;
// 默认成功的状态码为0
private String code = "0";
// 返回的消息,默认成功
private String message = "操作成功!";
// 保存任何数据类型的数据
private Object data;
public AjaxResult(){}
public AjaxResult(boolean success, String message){
this.success = success;
this.message = message;
}
public AjaxResult(boolean success, String code, String message){
this.success = success;
this.code = code;
this.message = message;
}
public AjaxResult(boolean success, String code, String message, Object data) {
this.success = success;
this.code = code;
this.message = message;
this.data = data;
}
public static AjaxResult success(){
return new AjaxResult();
}
public static AjaxResult success(String code, String message){
AjaxResult ajaxResult = new AjaxResult();
ajaxResult.setMessage(message);
ajaxResult.setCode(code);
return ajaxResult;
}
public static AjaxResult success(String code, String message, Object data){
AjaxResult ajaxResult = new AjaxResult();
ajaxResult.setMessage(message);
ajaxResult.setCode(code);
ajaxResult.setData(data);
return ajaxResult;
}
public static AjaxResult success(Object data){
AjaxResult ajaxResult = new AjaxResult();
ajaxResult.setData(data);
return ajaxResult;
}
public static AjaxResult error(GlobalExceptionEnum globalExceptionEnum){
AjaxResult ajaxResult = new AjaxResult();
ajaxResult.setSuccess(false);
ajaxResult.setMessage(globalExceptionEnum.getMessage());
ajaxResult.setCode(ajaxResult.getCode());
return ajaxResult;
}
public static AjaxResult error(String code, String message){
AjaxResult ajaxResult = new AjaxResult();
ajaxResult.setSuccess(false);
ajaxResult.setMessage(message);
ajaxResult.setCode(code);
return ajaxResult;
}
public static AjaxResult error(String code, String message, Object data){
AjaxResult ajaxResult = new AjaxResult();
ajaxResult.setSuccess(false);
ajaxResult.setMessage(message);
ajaxResult.setCode(code);
ajaxResult.setData(data);
return ajaxResult;
}
// 构建链式语法
public AjaxResult setSuccess(boolean success){
this.success = success;
return this;
}
public AjaxResult setMessage(String message){
this.message = message;
return this;
}
public AjaxResult setCode(String code){
this.code = code;
return this;
}
public AjaxResult setResultObj(Object data){
this.data = data;
return this;
}
}