@RestControllerAdvice是什么
@RestControllerAdvice 是 Spring 框架中的一个组合注解,结合了 @ControllerAdvice 和 @ResponseBody 的功能。它通常用于全局处理控制器(Controller)层抛出的异常,并统一返回结构化的响应结果(如 JSON 或 XML)
@RestControllerAdvice是Spring框架提供的一个注解,用于定义全局异常处理器和全局数据绑定设置。它结合了@ControllerAdvice和@ResponseBody两个注解的功能。
@ControllerAdvice
@ControllerAdvice是一个用于定义全局控制器增强(即全局异常处理和全局数据绑定)的注解。通过使用@ControllerAdvice,我们可以将异常处理和数据绑定逻辑集中到一个类中,避免在每个控制器中重复编写相同的异常处理代码。
@ResponseBody
@ResponseBody是用于指示控制器方法返回的对象将被直接写入响应体中的注解。它告诉Spring将方法的返回值序列化为JSON或其他适当的响应格式,并将其作为HTTP响应的主体返回给客户端。
@RestControllerAdvice作用
当我们在类上使用@RestControllerAdvice注解时,它相当于同时使用了@ControllerAdvice和@ResponseBody。这意味着被@RestControllerAdvice注解标记的类将被视为全局异常处理器,并且异常处理方法的返回值将以JSON格式直接写入响应体中。
通过在@RestControllerAdvice类中定义异常处理方法,我们可以捕获和处理控制器中抛出的异常,提供自定义的异常处理逻辑,以及返回适当的响应给客户端。这样可以统一处理应用程序中的异常情况,提高代码的可维护性和可读性。
未捕获全局异常页面效果
全局异常处理后页面效果
{
"code": 500,
"message": "交易时间不能为空",
"data": null
}
常见可被捕获的异常类型
下面是常见可以被@RestControllerAdvice捕获的异常类型
- Exception:普通的Java异常,是大多数其他异常的基类。
- RuntimeException:运行时异常,包括NullPointerException、IllegalArgumentException等。
- HTTP状态码异常:例如,HttpStatus.NOT_FOUND表示资源未找到,HttpStatus.BAD_REQUEST表示请求错误等。
- ValidationException:数据验证异常,例如使用JSR-303或Hibernate Validator进行的数据验证失败。
- MethodArgumentNotValidException:请求方法参数验证失败时抛出的异常。
- HttpMessageNotReadableException:当请求的HTTP消息无法读取或解析时抛出的异常,例如请求体格式错误。
- HttpRequestMethodNotSupportedException:当请求的HTTP方法不受支持时抛出的异常。
- MissingServletRequestParameterException:当请求缺少必需的参数时抛出的异常。
- BindException:数据绑定失败时抛出的异常,通常与表单提交相关。
- ConstraintViolationException:当使用Bean验证(如Hibernate Validator)时,违反约束条件时抛出的异常。
- NoHandlerFoundException:当找不到适合处理当前请求的处理程序时抛出的异常。
- 自定义的异常类
代码实现
统一返回bean
import lombok.Data;
import org.springframework.http.HttpStatus;
@Data
public class ResponseBean {
private int code;
private String message;
private Object data;
/**
* 默认成功消息
*/
public static final String DEFAULT_SUCCESS_MESSAGE = "成功";
/**
* 默认成功码
*/
public static final int DEFAULT_SUCCESS_CODE = HttpStatus.OK.value();
/**
* 默认错误码
*/
public static final int DEFAULT_ERROR_CODE = HttpStatus.INTERNAL_SERVER_ERROR.value();
}
组装统一请求返回
/**
* @desc 组装统一请求返回
* @date 2020-06-19
*/
public class ResponseUtil {
public static ResponseBean success() {
return success(null, ResponseBean.DEFAULT_SUCCESS_MESSAGE,null);
}
public static ResponseBean success(String successMessage) {
return success(null, successMessage,null);
}
public static ResponseBean success(Object data, String successMessage,Integer code) {
ResponseBean responseBean = new ResponseBean();
if (code != null) {
responseBean.setCode(code);
} else {
responseBean.setCode(ResponseBean.DEFAULT_SUCCESS_CODE);
}
responseBean.setMessage(successMessage);
responseBean.setData(data);
return responseBean;
}
public static ResponseBean error(int errorCode, String errorMessage, Object data) {
ResponseBean responseBean = new ResponseBean();
responseBean.setCode(errorCode);
responseBean.setMessage(errorMessage);
responseBean.setData(data);
return responseBean;
}
public static ResponseBean error(int errorCode, String errorMessage) {
return error(errorCode, errorMessage, null);
}
public static ResponseBean error(String errorMessage) {
return error(ResponseBean.DEFAULT_ERROR_CODE, errorMessage, null);
}
}
自定义的异常类
public class BaseException extends RuntimeException {
private static final long serialVersionUID = 1L;
//业务类型
private String bizType;
//业务代码
private int bizCode;
//错误信息
private String message;
public BaseException(String bizType, int bizCode, String message) {
super(message);
this.bizType = bizType;
this.bizCode = bizCode;
this.message = message;
}
public BaseException(String message) {
super(message);
this.bizType = "";
this.bizCode = ResponseBean.DEFAULT_ERROR_CODE;
this.message = message;
}
public BaseException(String bizType, String message) {
super(message);
this.bizType = bizType;
this.bizCode = ResponseBean.DEFAULT_ERROR_CODE;
this.message = message;
}
public BaseException(int bizCode, String message) {
super(message);
this.bizType = "";
this.bizCode = bizCode;
this.message = message;
}
public String getBizType() {
return bizType;
}
public void setBizType(String bizType) {
this.bizType = bizType;
}
public int getBizCode() {
return bizCode;
}
public void setBizCode(int bizCode) {
this.bizCode = bizCode;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
全局处理类
所有异常最终通过 handleException 方法封装为统一的 ResponseBean 对象。使用 ResponseBean.error(code, message) 返回结构化 JSON。
import com.alibaba.fastjson.JSONException;
import java.sql.SQLException;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import ResponseBean;
import BaseException;
import ResponseUtil;
import org.apache.catalina.connector.ClientAbortException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
/**
* @desc 统一异常处理
* @date 2020-06-19
*/
@RestControllerAdvice
@Slf4j
@ConditionalOnWebApplication
public class ExceptionAdvice {
/**
* 捕捉自定义异常
*/
@ExceptionHandler(BaseException.class)
@ResponseStatus(HttpStatus.OK)
public ResponseBean handleBaseException(HttpServletRequest request, Throwable ex) {
String message = ex.getMessage();
int code = ((BaseException) ex).getBizCode();
return handleException(code, message, request, ex);
}
/**
* 捕捉空指针异常
*/
@ExceptionHandler(NullPointerException.class)
@ResponseStatus(HttpStatus.OK)
public ResponseBean handleNullPointerException(HttpServletRequest request, Throwable ex) {
String message = "空指针异常";
return handleException(ResponseBean.DEFAULT_ERROR_CODE, message, request, ex);
}
/**
* 数据异常
*/
@ExceptionHandler(SQLException.class)
@ResponseStatus(HttpStatus.OK)
public ResponseBean handleSqlException(HttpServletRequest request, Throwable ex) {
String message = "数据异常";
return handleException(ResponseBean.DEFAULT_ERROR_CODE, message, request, ex);
}
/**
* 类型转换异常
*/
@ExceptionHandler(ClassCastException.class)
@ResponseStatus(HttpStatus.OK)
public ResponseBean handleCastException(HttpServletRequest request, Throwable ex) {
String message = "类型转换异常";
return handleException(ResponseBean.DEFAULT_ERROR_CODE, message, request, ex);
}
/**
* 参数异常
*/
@ExceptionHandler({
HttpMessageNotReadableException.class,
IllegalArgumentException.class,
BindException.class,
JSONException.class,
MissingServletRequestParameterException.class,
MethodArgumentTypeMismatchException.class
})
@ResponseStatus(HttpStatus.OK)
public ResponseBean handleParamException(HttpServletRequest request, Throwable ex) {
String message = "参数错误";
return handleException(ResponseBean.DEFAULT_ERROR_CODE, message, request, ex);
}
/**
* 请求方式异常
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
@ResponseStatus(HttpStatus.OK)
public ResponseBean handleRequestMethodException(HttpServletRequest request, Throwable ex) {
String message = "请求方式错误," + ex.getMessage();
return handleException(ResponseBean.DEFAULT_ERROR_CODE, message, request, ex);
}
/**
* 请求超时异常
*/
@ExceptionHandler(ClientAbortException.class)
@ResponseStatus(HttpStatus.OK)
public ResponseBean handleClientAbortException(HttpServletRequest request, Throwable ex) {
String message = "请求超时";
return handleException(ResponseBean.DEFAULT_ERROR_CODE, message, request, ex);
}
/**
* 资源访问异常
*/
@ExceptionHandler(ResourceAccessException.class)
@ResponseStatus(HttpStatus.OK)
public ResponseBean handleResourceAccessException(HttpServletRequest request, Throwable ex) {
String message = "数据集过大, 查询超时, 请缩小数据访问";
return handleException(ResponseBean.DEFAULT_ERROR_CODE, message, request, ex);
}
/**
* 参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.OK)
public ResponseBean handleMethodArgumentNotValidException(HttpServletRequest request, Throwable ex) {
final List<ObjectError> allErrors = (((MethodArgumentNotValidException) ex).getBindingResult()).getAllErrors();
final String collect = allErrors.stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(","));
String message = "参数校验失败," + collect;
return handleException(ResponseBean.DEFAULT_ERROR_CODE, message, request, ex);
}
/**
* 参数校验异常
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.OK)
public ResponseBean handleMConstraintViolationException(HttpServletRequest request, Throwable ex) {
String message = ex.getMessage();
String sepe = ": ";
if (StringUtils.isNotBlank(message)) {
StringBuilder sb = new StringBuilder("参数校验失败");
String[] errorMsgs = message.split(",");
for (String m : errorMsgs) {
int idx = m.indexOf(sepe) + sepe.length();
sb.append(",").append(m.substring(idx, m.length()));
}
message = sb.toString();
}
return handleException(ResponseBean.DEFAULT_ERROR_CODE, message, request, ex);
}
/**
* 捕捉其他所有异常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
public ResponseBean globalException(HttpServletRequest request, Throwable ex) {
//异常封装及反馈
String message = "系统异常, 请稍后重试!";
int code = 500;
return handleException(code, message, request, ex);
}
/**
* 处理异常,保存异常日志
*
* @param code
* @param message
* @param request
* @param ex
* @return
*/
private ResponseBean handleException(int code, String message, HttpServletRequest request, Throwable ex) {
//saveExceptionLog2DB();
log.error("ExceptionAdvice捕获异常如下,code:{}", code, ex);
return ResponseUtil.error(code, message);
}
}
适用场景
1.全局异常处理:当你的应用有多个REST API时,@RestControllerAdvice 就像是你的“全能安保团队”,可以集中处理所有API的异常,确保它们都在“守法”的范围内,不让任何一个小问题逃脱。
2.统一错误响应:保证所有的错误响应格式一致,就像在整个应用里安装了一台“错误统一机”,不管出什么错,大家都能以一种优雅的方式回家,前端处理变得像做数学题一样简单,不再需要猜测错误的“密码”。
3.简化代码:减少每个控制器中重复的异常处理代码,仿佛是给你的代码引入了“精简瘦身术”,让它们告别冗长的异常处理程序,变得轻盈如燕,从此代码更清爽,维护起来也不再像在跑马拉松。
注意事项
1.异常处理的优先级:确保你的 @RestControllerAdvice 类不会被其他异常处理器覆盖,就像在大选中确保你的候选人不会被其他“竞争者”挤下台。Spring会根据配置的优先级挑选出最合适的“超级英雄”,你可得确保你的英雄在“排行榜”上稳稳当当!
2.性能影响:虽然全局异常处理让代码更整洁,但记得不要在异常处理中搞得像大型音乐会一样复杂,这样可能会影响应用的性能。毕竟,异常处理不是要演一场“过度排练”的大戏,而是要快速、精准地解决问题。
3.详细错误信息:千万不要将过于详细的错误信息暴露给用户,这就像是把你的应用的“隐私小秘密”全盘托出一样,可能会带来安全隐患。最好给用户提供一个“迷你版”错误信息,既神秘又安全,确保不让应用的“秘密”泄露出去!
优点和缺点
优点:
-
集中管理:所有的异常处理逻辑集中在一个地方,就像是把你家里的所有杂物都丢进了一个大箱子里,管理起来简单又方便,不再需要在房间里到处找“丢失”的东西。
-
统一响应格式:提供一致的错误响应格式,就像是给每一个问题都穿上了同样的“制服”,不论错误多么五花八门,用户都能看到一份整洁的报告,提高了用户体验,就像每次吃饭都有固定的菜单一样舒心。
-
代码简洁:减少重复的异常处理代码,让业务逻辑更清晰,仿佛是把你代码中的“赘肉”统统剪掉,让它变得像模特一样苗条,业务逻辑也显得一目了然。
缺点:
-
全局处理的复杂性:在处理特定异常时,可能需要更多的判断逻辑,就像是在处理一场大派对时,你得考虑每个人的口味和需求,复杂的情况就像“宴会策划”的挑战,让人有点头疼。
-
性能开销:如果处理不当,异常处理可能对应用的性能产生影响。就像是你请了一个“高大上的保安”却让它在门口做过多的繁琐检查,最终可能让应用变得像个“大号的铁笼”,速度慢了点。
最佳实践
1.定义清晰的异常类:创建自定义异常类,就像在家里为每个问题配置专门的“急救箱”,这样可以处理特定类型的错误,避免使用那种“通用万用”的异常类。就像你不会用拖把去修车,特定问题还是要有专门的工具。
2.使用日志记录:在异常处理方法中记录详细的日志,就像给你的应用配备了一个“全能侦探”,无论出现什么问题,都会详细记录下来,帮助你在“事故现场”找到线索,绝对不让任何问题“逃脱”。
3.合理返回状态码:根据异常的类型返回合适的HTTP状态码,就像在面对不同类型的投诉时,提供不同的处理方案。这样可以确保用户收到的错误信息准确无误,就像在餐厅里点菜一样明确,避免让用户感到困惑。
4.提供用户友好的信息:确保错误信息对用户友好,但不要泄露敏感的内部信息,就像在开派对时,给客人送上贴心的小礼物,但不要让他们看到你的派对策划书。这样用户既能理解问题,又不会窥探到你的应用“幕后”的秘密。
总结
@RestControllerAdvice 就像是你代码中的超级防守球员,专门为你挡住那些讨厌的异常进攻。这个工具不仅能把所有异常集中处理,让你的代码更加优雅和整洁,还能确保你的异常响应保持一致。它就像是一位总是能在关键时刻出现在你身边的队员,让你不再为每一次异常而心烦。
当然,就像在篮球比赛中,你不希望你的防守明星把球场搞得一团乱,异常处理也是一样,保持简洁和高效是关键。不要让异常处理变成你代码中的“负担”,要让它像你球队里的防守明星一样,确保你的应用在异常面前保持坚如磐石!