背景
最近在Review Code时发现开发人员对接口开发的返回结果参差不齐,对后端服务的错误消息输出也是原生态的错误输出。对前端开发人员开发不友好,对用户使用感官也很差。对此寻求解决方案。
方案
使用Spring @RestControllerAdvice(返回增强器),统一处理控制层中未捕获异常处理的异常信息。
@RestControllerAdvice是Spring框架中一个用于统一处理控制器异常和返回结果的注解,可以捕获整个应用程序中抛出的异常,并对它们进行处理。它是@ControllerAdvice注解的特殊版本,用于RESTful风格的应用程序。
@RestControllerAdvice注解实际上是@ControllerAdvice和@ResponseBody注解的组合,这意味着当使用@RestControllerAdvice注解时,异常处理方法的返回值将自动转换为HTTP响应的主体,返回的数据是JSON格式的。因此,@RestControllerAdvice常用于全局异常的捕获处理和请求参数的增强,适用于所有使用@RequestMapping方法的情况。

实操
创建统一返回报文体
创建统一返回报文体 ResponseBody。 其中返回数据类型使用泛型进行接收,便于兼容多种类型数据的输出。
public class ResponseBody<T> {
/**
* 正确响应状态
*/
public static final int OK = 200;
/**
* 错误响应状态
*/
public static final int ERROR = 100;
public static <T> ResponseBody<T> successResponse(T data){
ResponseBody<T> response = new ResponseBody<>();
response.setStatus(ResponseBody.OK);
response.setData(data);
return response;
}
/**
* 响应数据
*/
private T data;
/**
* 状态值
*/
private int status;
/**
* 描述信息
*/
private String description;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
创建业务异常类
创建 DiyException 业务异常类,使已知业务异常信息可使用。
public class DiyException extends RuntimeException {
private String msg;
public DiyException(String msg) {
super(msg);
this.msg = msg;
}
/**
* 生成业务异常
*/
public static RuntimeException getDiyException(String msg) {
return new DiyException(msg);
}
}
统一异常拦截器
使用RestControllerAdvice创建统一异常拦截器,统一处理控制层中未捕获异常处理的异常信息。
@RestControllerAdvice(value = "com.loop.helloworld")//设置拦截范围
@Order(Ordered.HIGHEST_PRECEDENCE) //使用Oracle 驱使本拦截在最前避免与其他拦截器冲突
public class DemoGlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(DemoGlobalExceptionHandler.class);
/**
* @方法名称 ValidData 校验报错拦截
* @功能描述 <pre> ValidData 校验报错统一拦截返回 <pre>
* @作者 loop 猿小哥
* @创建时间 2022/3/29 17:46
*/
@ExceptionHandler({RuntimeException.class})
@ResponseStatus(HttpStatus.OK)
public ResponseBody<Object> handlerMethodArgumentNotValidException(Exception exception) {
String message = exception.getMessage();
this.logger.error(message, exception);
ResponseBody<Object> response = new ResponseBody<Object>();
response.setStatus(ResponseBody.ERROR);
if (StringUtils.isBlank(message)) {
message = "系统发生未知异常,请查看日志信息";
}
response.setStatus(ResponseBody.ERROR);
response.setDescription(message);
return response;
}
/**
* @方法名称 数据转换错误错误统一拦截返回
* @功能描述 <pre> 数据转换错误错误统一拦截返回 <pre>
* @作者 loop 猿小哥
* @创建时间 2022/3/29 17:46
*/
@ExceptionHandler({DiyException.class})
@ResponseStatus(HttpStatus.OK)
public ResponseBody<Object> handlerInvalidFormatException(DiyException exception) {
logger.debug("business exception:", exception);
logger.error(exception.getMessage(), exception);
ResponseBody<Object> response = new ResponseBody<Object>();
response.setStatus(ResponseBody.ERROR);
response.setDescription(exception.getMessage());
return response;
}
}
测试验证
创建Controller接口测试类
@RestController
@RequestMapping("/demo")
public class DemoController {
@PostMapping("/get")
public ResponseBody<Map> get(@RequestBody Map<String, Object> reqParams) {
Map<String, Object> responseVo = new HashMap<>();
if (reqParams.get("id").equals("0")){
throw new RuntimeException("数据错误");
}
if (reqParams.get("id").equals("1")){
if (reqParams.get("name").equals("验证未知的错误")){
System.out.println();
}
}
if (reqParams.get("id").equals("2")){
throw DiyException.getDiyException("业务错误");
}
return ResponseBody.successResponse(responseVo);
}
}
未开启增强,接口测试



测试未拦截情况可看出,接口http状态是500错误,返回报文信息不友好。
开启增强,接口测试



通过上面拦截测试情况,得出接口http状态是200错误,返回报文信息经过统一处理。系统发生未知错误时,也拦截进行输出友好的错误消息。
总结
通过上面案例可以看出@RestControllerAdvice使得异常处理方法的返回值自动转换为HTTP响应的主体,返回的数据是JSON格式的。使用@RestControllerAdvice可以统一处理控制器异常和返回结果,提高了代码的可读性和可维护性。
下面对ExceptionHandler 统一异常拦截器进行一些要点总结:
- @RestControllerAdvice注解中的value值可进行设置Controller拦截范围
- @Order 注解可设置拦截器级别(Ordered.HIGHEST_PRECEDENCE 为最高级)。
- 方法上的@ExceptionHandler 注解设置需要处理的异常类型,方法入参为需处理的异常类。
- 方法上的@ResponseStatus注解可进行设置接口 http响应状态。