当前位置:   article > 正文

SpringBoot优雅地处理全局异常,返回前端_springboot抛出异常给前端

springboot抛出异常给前端

笔者这边提供了两种处理全局异常的方式。这两种方式各有千秋,都很优雅。至于伙伴们想用哪种方式,那就仁者见仁,智者见智了。

0、公共部分

在介绍异常处理方式前,先定义一些公共的类。这些类在两种处理方式中都会用到。

【自定义业务异常】

  1. /**
  2. * 自定义业务异常
  3. */
  4. @Data
  5. public class SunException extends RuntimeException {
  6. private Integer code;
  7. private String msg;
  8. public SunException(SystemEnum systemEnum) {
  9. this.code = systemEnum.getCode();
  10. this.msg = systemEnum.getDesc();
  11. }
  12. public SunException(BusinessEnum businessEnum) {
  13. this.code = businessEnum.getCode();
  14. this.msg = businessEnum.getDesc();
  15. }
  16. public SunException(Integer code, String msg) {
  17. this.code = code;
  18. this.msg = msg;
  19. }
  20. }

【自定义系统枚举】

  1. /**
  2. * 系统枚举
  3. */
  4. public enum SystemEnum {
  5. SUCCESS(0, "success"),
  6. FAIL(-1, "fail"),
  7. PARAM_ILLEGAL(100, "参数非法!"),
  8. SERVICE_TIME_OUT(200, "服务间调用超时"),
  9. UNEXPECTED_EXCEPTION(500, "系统内部错误,请联系管理员!"),
  10. OTHER(9999, "Unknown Exception.");
  11. private Integer code;
  12. private String desc;
  13. SystemEnum(Integer code, String desc) {
  14. this.code = code;
  15. this.desc = desc;
  16. }
  17. public Integer getCode() {
  18. return code;
  19. }
  20. public String getDesc() {
  21. return desc;
  22. }
  23. }

【自定义业务枚举】

  1. /**
  2. * 业务类枚举
  3. *
  4. * @author: dong
  5. * @date: 2023/2/12 21:50
  6. * @since: 1.0
  7. */
  8. public enum BusinessEnum {
  9. /**--------用户相关----------**/
  10. USER_ID_NOT_EXIST(1000, "userId not exist."),
  11. /**--------任务相关----------**/
  12. TASK_ID_NOT_EXIST(2000, "taskId not exist."),
  13. TASK_NAME_NOT_EXIST(2001, "taskName not exist."),
  14. TASK_TYPE_NOT_EXIST(2002, "taskType not exist."),
  15. DEADLINE_NOT_EXIST(2003, "deadline not exist."),
  16. CONTENT_NOT_EXIST(2004, "content not exist.")
  17. ;
  18. private Integer code;
  19. private String desc;
  20. BusinessEnum(Integer code, String desc) {
  21. this.code = code;
  22. this.desc = desc;
  23. }
  24. public Integer getCode() {
  25. return code;
  26. }
  27. public void setCode(Integer code) {
  28. this.code = code;
  29. }
  30. public String getDesc() {
  31. return desc;
  32. }
  33. public void setDesc(String desc) {
  34. this.desc = desc;
  35. }
  36. }

【统一封装响应体】

  1. /**
  2. * 统一封装响应体
  3. * @param <T>
  4. */
  5. public class BaseResult<T> {
  6. private Integer code;
  7. private String msg;
  8. private T data;
  9. public BaseResult() {
  10. }
  11. public BaseResult(Integer code, String msg, T data) {
  12. this.code = code;
  13. this.msg = msg;
  14. this.data = data;
  15. }
  16. public BaseResult(T data) {
  17. this.code = SystemEnum.SUCCESS.getCode();
  18. this.msg = SystemEnum.SUCCESS.getDesc();
  19. this.data = data;
  20. }
  21. public Integer getCode() {
  22. return code;
  23. }
  24. public void setCode(Integer code) {
  25. this.code = code;
  26. }
  27. public String getMsg() {
  28. return msg;
  29. }
  30. public void setMsg(String msg) {
  31. this.msg = msg;
  32. }
  33. public T getData() {
  34. return data;
  35. }
  36. public void setData(T data) {
  37. this.data = data;
  38. }
  39. }

【响应结果工具类】

  1. /**
  2. * 响应结果工具类
  3. */
  4. public class ResultUtil {
  5. public static <R> BaseResult<R> outSuccess() {
  6. return new BaseResult<>(SystemEnum.SUCCESS.getCode(), SystemEnum.SUCCESS.getDesc(), null);
  7. }
  8. public static <R> BaseResult<R> outSuccess(R data) {
  9. return new BaseResult<>(data);
  10. }
  11. public static <R> BaseResult<R> outFail(String errorMsg) {
  12. return new BaseResult<>(SystemEnum.FAIL.getCode(), errorMsg, null);
  13. }
  14. public static <R> BaseResult<R> outFail(Integer errorCode, String errorMsg) {
  15. return new BaseResult<>(errorCode, errorMsg, null);
  16. }
  17. }

方式一、@RestControllerAdvice + @ExceptionHandler

如下所示,

a. 新建一个全局异常处理类,并在类名前加上@RestControllerAdvice注解,该注解可以拦截项目中抛出的异常;

b. 同时在新建一个处理异常的方法,并在方法上加上@ExceptionHandler注解,并在该注解的属性中指定具体的异常。如下代码中指定的具体异常即是 SunException(也就是上一小节中笔者自定义的业务异常)。

c. 在处理异常的方法中,通过ResultUtil.outFail() 方法统一封装返回给前端的响应体。

  1. /**
  2. * 全局异常处理
  3. */
  4. @RestControllerAdvice
  5. public class GlobalExceptionHandler {
  6. private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
  7. @ExceptionHandler(SunException.class)
  8. @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
  9. public BaseResult handlerBusinessException(SunException sunException) {
  10. LOGGER.error("exception happened at {}", sunException.getMsg());
  11. return ResultUtil.outFail(sunException.getCode(), sunException.getMsg());
  12. }
  13. }

那么在具体的业务接口中如何抛出异常能被GlobalExceptionHandler所捕获呢?

其实很简单,只要用 throw new SunException(...); 就可以了。注意:这里只能抛出SunException,不能抛出RuntimeException或者任何其他异常。因为@ExceptionHandler已经指定了具体的异常类型。

  1.     @GetMapping("/hello")
  2. public String sayHello(String name) {
  3. if (StringUtils.isEmpty(name)) {
  4. throw new SunException(SystemEnum.PARAM_ILLEGAL);
  5. }
  6. return "The sun is rising.";
  7. }

Swagger测试效果:

方式二、AOP实现

很明显,aop天生就是干这个的料。aop在业务解耦方面简直如鱼得水,像统一打印日志,统一捕获异常等等。talk is cheap,show me the code.

如下代码所示:

a. 新建这个aop监控类,新增切点,切所有模块的Controller层的所有方法;

b. 实现一个环绕通知接口,打印方法入参以及请求结果;

c. 注意代码40行到45行,就是捕获项目中所有的SunException,并封装成统一的响应体返回前端。

  1. /**
  2. * aop监控类
  3. **/
  4. @Slf4j
  5. @Aspect
  6. @Component
  7. public class AspectMonitor {
  8. /**
  9. * 日志切点,切所有模块controller层中的所有方法
  10. */
  11. @Pointcut("execution(* com.bxbro.*..controller..*.*(..))")
  12. public void logPointCut() {
  13. // do nothing.
  14. }
  15. /**
  16. * 对接口做统一的日志及异常处理
  17. * @param pjp
  18. * @return
  19. */
  20. @Around("logPointCut()")
  21. public Object apiMonitor(ProceedingJoinPoint pjp) {
  22. Object[] args = pjp.getArgs();
  23. Object[] arguments = new Object[args.length];
  24. for (int i=0;i<args.length;i++) {
  25. // ServletRequest不能序列化,从入参里排除,
  26. // 否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
  27. if(args[i] instanceof ServletRequest || args[i] instanceof ServletResponse || args[i] instanceof MultipartFile) {
  28. continue;
  29. }
  30. arguments[i] = args[i];
  31. }
  32. log.info("请求入参:{},方法名:{}.{}", JSON.toJSONString(arguments),pjp.getSignature().getDeclaringTypeName(),pjp.getSignature().getName());
  33. Object result;
  34. try {
  35. result = pjp.proceed();
  36. } catch (Throwable throwable) {
  37. if (throwable instanceof SunException) {
  38. SunException sunException = (SunException) throwable;
  39. return ResultUtil.outFail(sunException.getCode(), sunException.getMsg());
  40. }
  41. log.error("接口请求异常:{}", throwable);
  42. return ResultUtil.outFail("系统内部错误" + throwable.getMessage());
  43. }
  44. log.info("请求结果:{}", JSON.toJSONString(result));
  45. return result;
  46. }
  47. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/516799
推荐阅读
相关标签
  

闽ICP备14008679号