版本需求: SpringBoot 2.0+
(一) 概述
Java项目中处理异常方式无非两种,要么执行trycatch操作,要么执行throw操作(抛给其它对象处理),无论采用哪种方式,其目的是让我们的系统对异常要有反馈。但现在的问题是我们如何让这种反馈代码的编写即简单又直观、友好。
(二) 处理规范
我们在处理异常的过程中通常要遵循一定的设计规范,例如:
- 捕获异常时与抛出的异常必须完全匹配,或者捕获异常是抛出异常的父类类型。
- 避免直接抛出RuntimeException,更不允许抛出Exception或者Throwable,应使用有业务含义的自定义异常(例如CustomeException)。
- 捕获异常后必须进行处理(例如记录日志)。如果不想处理它,需要将异常抛给它的调用者。
- 最外层的逻辑必须处理异常,将其转化成用户可以理解的内容。
- 避免出现重复的代码(Don't Repeat Yourself),即DAY原则。
(三) SpringBoot 工程下的异常处理
(1) 自己try异常处理
// 1) 捕捉自己自己处理
try {
// 业务处理
} catch(ArithmeticException e){
// log日志打印异常信息
log.error("【异常】异常描述>"+e);
// 处理异常
}
// 2) 捕捉异常 异常信息格式化/转换 但是自己不处理异常 抛出给上层处理
try {
// 业务处理
} catch(ArithmeticException e){
throw new CustomException(SysCode.EXE,"追加信息");// CustomeException为自定义异常
}
// 3) 流处理 try-with-resources JDK1.7 (可以省略finally中关闭流操作)
try (MyFileOutputStream file = new MyFileOutputStream("/home/test.gpg");
MyZipOutputStream o = new MyZipOutputStream(file)
) {
o.read();
} catch (Exception e) {
// log日志打印异常信息
log.error("【异常】异常描述>"+e);
// 处理异常
}
(2) Controller层面异常处理(不常用)
针对可能出问题的Controller,新增注解方法@ExceptionHandler
.
@Controller
public class TestController {
@GetMapping("/demo")
@ResponseBody
public R<String> demo(){
int i = 1 / 0;
return R.ok();
}
@ExceptionHandler({RuntimeException.class})
public R<String> REHandler(Exception e){
// 处理异常
log.error("【异常】异常描述>"+e);
return R.exe(e.getMessage());
}
}
注意事项: 1. 一个Controller下多个@ExceptionHandler上的异常类型不能出现一样的,否则运行时抛异常.
(3) 全局异常处理
1) 全局处理器(可扩展)
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* @Title: BindExceptionHandler
* @Description:处理Get请求中 使用@Valid 验证路径中请求实体校验失败后抛出的异常,详情继续往下看代码
* @param e
* @return
*/
@ExceptionHandler(BindException.class)
@ResponseBody
public Response<String> BindExceptionHandler(BindException e) {
String message = e.getBindingResult().getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
log.error("【异常】参数校验>>>" + e.getMessage());
return R.fire(SysCode.UNQUALIFIED_PARAMETERS, message);
}
/**
* @Title: ConstraintViolationExceptionHandler
* @Description: 处理请求参数格式错误 @RequestParam上validate失败后抛出的异常是javax.validation.ConstraintViolationException
* @param e
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
public Response<String> ConstraintViolationExceptionHandler(ConstraintViolationException e) {
String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage)
.collect(Collectors.joining());
log.error("【异常】参数校验>>>" + e.getMessage());
return R.fire(SysCode.UNQUALIFIED_PARAMETERS, message);
}
/**
* @Title: MethodArgumentNotValidExceptionHandler
* @Description: 数格式错误 @RequestBody上validate失败后抛出的异常是MethodArgumentNotValidException异常。
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public Response<String> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.joining());
log.error("【异常】参数校验>>>" + e.getMessage());
return R.fire(SysCode.UNQUALIFIED_PARAMETERS, message);
}
/**
* @Title: IllegalArgumentException
* @Description: 参数校验异常(适用使用Assert断言)
* @param e
* @return
*/
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public Response<String> IllegalArgumentExceptionHandler(Exception e) {
log.error("【异常】参数校验>>>" + getExceptionInfo(e));
return R.fire(SysCode.UNQUALIFIED_PARAMETERS, e.getMessage());
}
/**
* @Title: ExceptionHandler
* @Description: 统一异常处理
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public Response<String> ExceptionHandler(Exception e) {
log.error("【异常】全局捕捉>>>" + getExceptionInfo(e));
return R.exc(e.getMessage());
}
/**
* @Title: getExceptionInfo
* @Description: 异常日志详细信息
* @param ex
* @return
*/
private static String getExceptionInfo(Exception ex) {
ByteArrayOutputStream out = null;
PrintStream printStream = null;
String rs = null;
try {
out = new ByteArrayOutputStream();
printStream = new PrintStream(out);
ex.printStackTrace(printStream);
rs = new String(out.toByteArray());
} catch (Exception e) {
log.error("GlobalExceptionHandler获取异常信息异常", e);
} finally {
if(null != printStream) printStream.close();
if(null != out) {
try {
out.close();
} catch (IOException e) {
log.error("GlobalExceptionHandler关闭输出流异常", e);
}
}
}
return rs;
}
/**
* @Title: customExceptionHandler
* @Description: 自定义异常
* @param customException
* @return
*/
@ExceptionHandler(CustomException.class)
@ResponseBody
public Response<String> customExceptionHandler(CustomException customException) {
return R.fire(customException.getCode(), customException.getMessage(), null, null);
}
}
2) 自定义异常(仅供参考)
public class CustomException extends RuntimeException {
private static final long serialVersionUID = -3772197636066585649L;
private int code;
private String message;
public CustomException(int code, String message) {
this.code = code;
this.message = message;
}
public CustomException(SysCode code) {
this.code = code.getCode();
this.message = code.getMessage();
}
// 省略getter/setter
}
3) 统一响应类(仅供参考)
public class R<T> implements Serializable {
private static final long serialVersionUID = 742186357541909508L;
/** 响应编码 */
private int code;
/** 响应描述 */
private String detail;
/** 响应内容 */
private T data;
/**
* 成功
*
* @param data 结果
* @return R
*/
public static <T> R<T> ok(T data) {
return new R<T>(SysCode.SUCC, data);
}
/**
* 异常
*
* @return R
*/
public static <T> R<T> exc() {
return new R<T>(SysCode.EXCEPTION, null);
}
/**
* 异常
*
* @param obj 信息体
* @return R
*/
public static <T> R<T> exc(Object obj) {
return new R<T>(SysCode.EXCEPTION, obj);
}
/**
* 失败
*
* @return R
*/
public static <T> R<T> fail() {
return new R<T>(SysCode.FAIL, null);
}
/**
* 失败
*
* @param obj 信息体
* @return R
*/
public static <T> R<T> fail(Object obj) {
return new R<T>(SysCode.FAIL, obj);
}
/**
* 失败
*
* @param sysCode 返回码枚举
* @return R
*/
public static <T> R<T> fire(SysCode code) {
return new R<T>(code);
}
/**
* 自定义失败(基本不用)
*
* @param code 失败编码
* @param detail 失败描述
* @param data 结果
* @param remark 备注
* @return R
*/
public static <T> R<T> fire(int code, String detail, T data) {
return new R<T>(code, detail, data);
}
/**
* 自定义失败
*
* @param code 失败编码
* @param detail 失败描述
* @return R
*/
public static <T> R<T> fire(int code, String detail) {
return new R<T>(code, detail);
}
public R() {
}
public R(SysCode code) {
this.code = code.getCode();
this.detail = code.getMsg();
}
public R(SysCode code, T data) {
this.code = code.getCode();
this.detail = code.getMsg();
this.data = data;
}
public R(int code, String detail) {
this.code = code;
this.detail = detail;
}
// 省略getter/setter
}
4) 状态码(仅供参考)
public enum SysCode {
SUCC(0, "成功"),
FAIL(1, "失败"),
EXCEPTION(999999, "系统异常");
private int code;
private String msg;
//省略getter/setter和构造函数
}
5) 使用
// 1) 系统抛出异常
public void test() throws Exception {
//do something
}
// 2) 主动抛出自定义异常
public void test2(int a,int b) {
//do something
if(a != b){
throw new CustomException(SysCode.EXCEPTION);
}
}
// 3) @Valid参数校验异常
public void test3(@Valid @NotBlank(message="用户名不能为空") String username) {
//do something
}
// 4) Assert断言
String userId = null;
Assert.notNull(userId, "用户信息不能为空");
// 5)其他系统运行时异常或未知异常