@RestControllerAdvice 全局异常处理

@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捕获的异常类型

  1. Exception:普通的Java异常,是大多数其他异常的基类。
  2. RuntimeException:运行时异常,包括NullPointerException、IllegalArgumentException等。
  3. HTTP状态码异常:例如,HttpStatus.NOT_FOUND表示资源未找到,HttpStatus.BAD_REQUEST表示请求错误等。
  4. ValidationException:数据验证异常,例如使用JSR-303或Hibernate Validator进行的数据验证失败。
  5. MethodArgumentNotValidException:请求方法参数验证失败时抛出的异常。
  6. HttpMessageNotReadableException:当请求的HTTP消息无法读取或解析时抛出的异常,例如请求体格式错误。
  7. HttpRequestMethodNotSupportedException:当请求的HTTP方法不受支持时抛出的异常。
  8. MissingServletRequestParameterException:当请求缺少必需的参数时抛出的异常。
  9. BindException:数据绑定失败时抛出的异常,通常与表单提交相关。
  10. ConstraintViolationException:当使用Bean验证(如Hibernate Validator)时,违反约束条件时抛出的异常。
  11. NoHandlerFoundException:当找不到适合处理当前请求的处理程序时抛出的异常。
  12. 自定义的异常类

代码实现

统一返回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 就像是你代码中的超级防守球员,专门为你挡住那些讨厌的异常进攻。这个工具不仅能把所有异常集中处理,让你的代码更加优雅和整洁,还能确保你的异常响应保持一致。它就像是一位总是能在关键时刻出现在你身边的队员,让你不再为每一次异常而心烦。

当然,就像在篮球比赛中,你不希望你的防守明星把球场搞得一团乱,异常处理也是一样,保持简洁和高效是关键。不要让异常处理变成你代码中的“负担”,要让它像你球队里的防守明星一样,确保你的应用在异常面前保持坚如磐石!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值