SpringBoot-23-全局异常机制+RESTful统一规范
1.为什么需要全局异常机制?
如果我们团队在开发项目的时候,程序出现异常是正常的,比如因为业务操作没有按照流程,程序的运行异常等等,我们不可能也不应该每一处异常进行单独处理,或者不处理将异常信息直接抛出给用户,这样会导致用户体验差。
因此设置统一的异常处理机制具有以下好处:
-
输出日志可以增强log的可读性,对我们自己排除bug也增加优势, -
提高用户体验性 -
降低前后端开发和维护成本(如果每一个后台开发抛出一场形式不一样,没有统一规范,前端每一个请求都会有一套处理异常逻辑,代码质量低,维护成本高)
那么我们需要如何的开发规范呢?
-
Service、Controlle等层捕获的异常,需要转换为自定义异常,然后对外抛出 -
设置统一的RESTful规范,常见的状态使用http状态,如果是业务比较复杂的,比如智能制造类系统,也要设置自定义的状态和相对应的message。 -
将捕获的异常message信息,通过自定义异常转换为易读易懂的message信息。 -
获取catch的时候,不要直接Exception,要尽可能分类exception,使得异常变得清晰。
之前章节也讲过
不熟悉的朋友可以去看一下。
代码实现
RESTful统一返回规范设置
我们在上一章节实际上已经介绍过了,但是怕一些人没有看过,这里再进行介绍一次
-
设置统一的IResultCode返回码接口
/**
* 统一返回结果接口
*/
public interface IResultCode {
/**
* 返回码
*
* @return int
*/
int getCode();
/**
* 返回消息
*
* @return String
*/
String getMsg();
}
-
设置ResultCode的返回码接口的实现
@Getter
@AllArgsConstructor
public enum ResultCode implements IResultCode{
/**
* 操作成功
*/
SUCCESS(200, "操作成功"),
/**
* 业务异常
*/
FAILURE(400, "业务异常"),
/**
* 服务异常
*/
ERROR(500, "服务异常"),
/**
* 参数错误
*/
GLOBAL_PARAM_ERROR(4000, "参数错误");
/**
* 状态码
*/
final int code;
/**
* 消息内容
*/
final String msg;
}
-
统一返回结果Result
@Data
@Getter
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
private int code;
private String msg;
private long time;
private T data;
private Result() {
this.time = System.currentTimeMillis();
}
private Result(IResultCode resultCode) {
this(resultCode, null, resultCode.getMsg());
}
private Result(IResultCode resultCode, String msg) {
this(resultCode, null, msg);
}
private Result(IResultCode resultCode, T data) {
this(resultCode, data, resultCode.getMsg());
}
private Result(IResultCode resultCode, T data, String msg) {
this(resultCode.getCode(), data, msg);
}
private Result(int code, T data, String msg) {
this.code = code;
this.data = data;
this.msg = msg;
this.time = System.currentTimeMillis();
}
/**
* 返回状态码
*
* @param resultCode 状态码
* @param <T> 泛型标识
* @return ApiResult
*/
public static <T> Result<T> success(IResultCode resultCode) {
return new Result<>(resultCode);
}
public static <T> Result<T> success(String msg) {
return new Result<>(ResultCode.SUCCESS, msg);
}
public static <T> Result<T> success(IResultCode resultCode, String msg) {
return new Result<>(resultCode, msg);
}
public static <T> Result<T> data(T data) {
return data(data, "处理成功");
}
public static <T> Result<T> data(T data, String msg) {
return data(ResultCode.SUCCESS.code, data, msg);
}
public static <T> Result<T> data(int code, T data, String msg) {
return new Result<>(code, data, data == null ? "承载数据为空" : msg);
}
public static <T> Result<T> fail() {
return new Result<>(ResultCode.FAILURE, ResultCode.FAILURE.getMsg());
}
public static <T> Result<T> fail(String msg) {
return new Result<>(ResultCode.FAILURE, msg);
}
public static <T> Result<T> fail(int code, String msg) {
return new Result<>(code, null, msg);
}
public static <T> Result<T> fail(IResultCode resultCode) {
return new Result<>(resultCode);
}
public static <T> Result<T> fail(IResultCode resultCode, String msg) {
return new Result<>(resultCode, msg);
}
public static <T> Result<T> condition(boolean flag) {
return flag ? success("处理成功") : fail("处理失败");
}
}
全局异常处理
-
设置BaseException作为全局基础异常和特定的验证码异常处理类
@Data
public class BaseException extends RuntimeException {
private static final long serialVersionUID = 5782968730281544562L;
private int status = INTERNAL_SERVER_ERROR.value();
public BaseException() {
}
public BaseException(String message) {
super(message);
}
public BaseException(int status, String message) {
super(message);
this.status = status;
}
}
@Data
public class ValidateCodeException extends RuntimeException {
private static final long serialVersionUID = -7285211528095468156L;
private int status = INTERNAL_SERVER_ERROR.value();
public ValidateCodeException() {
}
public ValidateCodeException(String msg) {
super(msg);
}
public ValidateCodeException(int code, String message) {
super(message);
this.status = code;
}
}
-
创建全局异常处理类BaseExceptionHandler
使用**@RestControllerAdvice+@ExceptionHandler**
具体介绍可以看我的全局异常处理介绍这里只说实现过程
@Slf4j
@ResponseBody
@RestControllerAdvice
public class BaseExceptionHandler {
/**
* BaseException 异常捕获处理
* @param ex 自定义BaseException异常类型
* @return Result
*/
@ExceptionHandler(BaseException.class)
public Result<?> handleException(BaseException ex) {
log.error("程序异常:" + ex.toString());
return Result.fail(ex.getStatus(), ex.getMessage());
}
/**
* BaseException 异常捕获处理
* @param ex 自定义BaseException异常类型
* @return Result
*/
@ExceptionHandler(ValidateCodeException.class)
@ResponseStatus
public Result<?> handleValidateCodeException(ValidateCodeException ex) {
log.error("验证码错误:" + ex.getMessage());
return Result.fail(ex.getStatus(), ex.getMessage());
}
/**
* FileNotFoundException,NoHandlerFoundException 异常捕获处理
* @param exception 自定义FileNotFoundException异常类型
* @return Result
*/
@ExceptionHandler({FileNotFoundException.class, NoHandlerFoundException.class})
public Result<?> noFoundException(Exception exception) {
log.error("程序异常==>errorCode:{}, exception:{}", HttpStatus.NOT_FOUND.value(), exception.getMessage());
return Result.fail(HttpStatus.NOT_FOUND.value(), exception.getMessage());
}
/**
* NullPointerException 空指针异常捕获处理
* @param ex 自定义NullPointerException异常类型
* @return Result
*/
@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<?> handleException(NullPointerException ex) {
log.error("程序异常:{}" + ex.toString());
return Result.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), ex.getMessage());
}
/**
* 通用Exception异常捕获
* @param ex 自定义Exception异常类型
* @return Result
*/
@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<?> handleException(Exception ex) {
log.error("程序异常:" + ex.toString());
String message = ex.getMessage();
if (StringUtils.contains(message, "Bad credentials")){
message = "您输入的密码不正确";
} else if (StringUtils.contains(ex.toString(), "InternalAuthenticationServiceException")) {
message = "您输入的用户名不存在";
}
return Result.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
}
}
控制层TestController的实现
@RequestMapping("test")
@RestController
public class TestController {
@GetMapping("/base/{name}")
public void BaseException(@PathVariable("name") String name) {
System.out.println("Hello: BaseException "+ name);
throw new BaseException(HttpStatus.MULTI_STATUS,"错误");
}
@GetMapping("/valid/{name}")
public void ValidateCodeException(@PathVariable("name") String name) {
System.out.println("Hello: ValidateCodeException "+ name);
throw new ValidateCodeException(ResultCode.GLOBAL_PARAM_ERROR.getCode(),ResultCode.GLOBAL_PARAM_ERROR.getMsg());
}
}
测试
使用postman分别测试
-
http://localhost:8080/test/base/hah -
http://localhost:8080/test/valid/hah
测试结果如下:
在上面图标记处不知道大家发现一个问题,自定义业务状态码和HttpStatus状态码存在不一致的情况,如果相应自定义的业务状态码,在HttpStatus存在,相应HttpStatus和自定义状态码一致需要怎么办呢?
HTTP和自定义状态码一致代码实现
@Component
@ControllerAdvice
public class GlobalResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
//如果响应结果是JSON数据类型
if(selectedContentType.equalsTypeAndSubtype(
MediaType.APPLICATION_JSON)){
int code= ((Result) body).getCode();
if(code>0 && code<512) {
//HTTP响应结果设置状态码,状态码就是IResultCode的code,二者达到统一
response.setStatusCode(
HttpStatus.valueOf(((Result) body).getCode())
);
}
return body;
}
return body;
}
}
测试结果:
如果您觉得本文不错,欢迎关注,点赞,收藏支持,您的关注是我坚持的动力!
原创不易,转载请注明出处,感谢支持!如果本文对您有用,欢迎转发分享!
原文始发于微信公众号(springboot葵花宝典):SpringBoot-23-全局异常机制+RESTful统一规范
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/184373.html