zoukankan      html  css  js  c++  java
  • SpringBoot 全局异常处理

    本文介绍使用@ControllerAdvice对Controller抛出的异常进行统一拦截和处理。

    定义返回格式

    @Getter
    @Setter
    @AllArgsConstructor
    public class UnifyResponse {
      private int code;
      private String message;
      private String request;
    }
    

    首先定义一个统一的返回格式,所有的异常最终都按照统一格式返回给前端。

    定义状态码

    不同的异常对应不同的返回状态码

    # exception-code.properties
    demo.codes[0] = ok
    demo.codes[9999] = 服务器未知异常
    demo.codes[10000] = 通用错误
    demo.codes[10001] = 通用参数错误
    demo.codes[10002] = 资源未找到
    

    首先将状态码集中在配置文件中进行管理

    properties的编码格式需要配置,否则可能出现中文乱码

    IDEA中Preferences -> File Encodings -> Default encoding for properties files 设置为UTF-8,同时勾上Transparent native-to-ascii conversion

    @Getter
    @Setter
    @ConfigurationProperties("demo")
    @PropertySource("classpath:config/exception-code.properties")
    @Component
    public class ExceptionCodeConfiguration {
      private Map<Integer, String> codes = new HashMap<>();
    
      public String getMessage(int code) {
        return codes.get(code);
      }
    }
    

    其次使用类ExceptionCodeConfiguration实现对配置文件的访问,@PropertySource可以用来指定配置文件路径,@ConfigurationProperties用于指定配置前缀。

    在配置类中,我们首先定义一个Map变量codes,这里的codes对应于配置文件中的codes,SpringBoot会在启动时自动将配置文件读入配置类中,最后需要使用@Component将配置类加入容器。

    public final class ExceptionCode {
      public static final int UNKNOWN = 9999;
      public static final int COMMON = 10000;
      public static final int PARAMS_ERROR = 10001;
      public static final int RESOURCE_NOT_FOUND = 10002;
    }
    

    最后定义一个”常量类“便于状态码的调用,避免直接填入数字。

    通用异常

    @ControllerAdvice根据异常类型的匹配程度选择相应的异常处理类,如果其他的handler都没有匹配到则使用通用的异常处理类。

    @RestControllerAdvice
    public class GlobalExceptionAdvice {
      @Autowired
      private ExceptionCodeConfiguration codeConfiguration;
    
      @ExceptionHandler(Exception.class)
      @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
      public UnifyResponse handleException(HttpServletRequest req, Exception e) {
        return new UnifyResponse(ExceptionCode.UNKNOWN, codeConfiguration.getMessage(ExceptionCode.UNKNOWN), this.getRequest(req));
      }
      
      // 获取请求方法和路径
      private String getRequest(HttpServletRequest req) {
        String url = req.getRequestURI();
        String method = req.getMethod();
        return method + " " + url;
      }
    }
    

    首先要在全局异常处理类上标注@ControllerAdvice注解,这里使用@RestControllerAdvice避免在方法上添加@ResponseBody

    然后定义一个方法handleException,同时在方法上添加@ExceptionHandler(value=Class<? extends Throwable>)注解,注解接收一个异常类型,表示该方法要处理什么异常,异常类型越具体,能够处理的范围越小。方法handleException接收HttpServletRequestException两个参数类型,用于接收数据进行处理。

    最终,对数据进行处理并打包成UnifyResponse进行返回。其中codeConfiguration是我们前面定义的异常码配置类,对应于我们自定义的异常码配置文件,通过这种方式我们可以获得code对应的message。

    自定义异常

    通用异常处理一般是处理意料之外的异常,对于开发者有意抛出的异常,我们可以单独定义相应的异常类型便于使用和处理。

    定义异常

    @Getter
    public class HttpException extends RuntimeException {
        protected Integer code;
        protected Integer httpStatusCode = 500;
    }
    
    public class NotFoundHttpException extends HttpException {
      public NotFoundHttpException(int code) {
        this.code = code;
        this.httpStatusCode = 404;
      }
    }
    

    这里我们自定义了HttpException用于开发者主动抛出,HttpException继承自RuntimeException,由于我们抛出异常后会被统一拦截处理,不希望编译期间进行检查,所以使用RuntimeException

    我们可以根据不同的HTTP状态码定义不同的异常类型,这里我们定义了NotFoundHttpException,他的状态码为404,实际调用时我们只需要抛出相应异常并填入自定义状态码即可。

    抛出自定义异常

    @RestController
    @Validated
    public class UserController {
      @GetMapping("/user/{id}")
      public String getUser(@PathVariable @Max(20) Integer id, @RequestParam @Length(min = 2, max = 10) String name) {
        throw new NotFoundHttpException(ExceptionCode.RESOURCE_NOT_FOUND);
      }
    

    假设没有找到用户,我们直接抛出NotFoundHttpException并填入自定义code即可完成错误处理。

    全局异常处理

    @RestControllerAdvice
    public class GlobalExceptionAdvice {
      @Autowired
      private ExceptionCodeConfiguration codeConfiguration;
      
      @ExceptionHandler(HttpException.class)
      public ResponseEntity<UnifyResponse> handleHttpException(HttpServletRequest req, HttpException e) {
        int code = e.getCode();
        UnifyResponse response = new UnifyResponse(code, codeConfiguration.getMessage(code), this.getRequest(req));
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        HttpStatus httpStatus = HttpStatus.resolve(e.getHttpStatusCode());
        return new ResponseEntity<>(response, httpHeaders, httpStatus);
      }
      
      // 获取请求方法和路径
      private String getRequest(HttpServletRequest req) {
        String url = req.getRequestURI();
        String method = req.getMethod();
        return method + " " + url;
      }
    }
    

    自定义HttpException的处理比较特殊,因为不同的HttpException要返回不同的HttpStatusCode,所有需要使用ResponseEntity<UnifyResponse>进行返回。

    如上文所示,我们需要添加@RestControllerAdvice @ExceptionHandler(HttpException.class)注解,其次我们需要分别定义UnifyResponsehttpHeadershttpStatus,最终将结果包装进ResponseEntity进行返回。

    参数校验异常

    参数校验分为两类,一类是PathVariables,RequesetParams的校验,返回ConstraintViolationException;另一类是RequestBody的校验,返回MethodArgumentNotValidException

    我们需要分别定义这两类异常的异常处理方法并获取信息返回UnifyResponse

    @RestControllerAdvice
    public class GlobalExceptionAdvice {
      @Autowired
      private ExceptionCodeConfiguration codeConfiguration;
    
      @ExceptionHandler(ConstraintViolationException.class)
      @ResponseStatus(HttpStatus.BAD_REQUEST)
      public UnifyResponse handleConstraintException(HttpServletRequest req, ConstraintViolationException e) {
        StringBuilder messageBuilder = new StringBuilder();
        e.getConstraintViolations().forEach(violation -> {
          String param = violation.getPropertyPath().toString().replaceAll("\w+\.", "");
          String message = violation.getMessage();
          messageBuilder.append(param).append(":").append(message).append(";");
        });
        return new UnifyResponse(ExceptionCode.PARAMS_ERROR, messageBuilder.toString(), this.getRequest(req));
      }
    
      @ExceptionHandler(MethodArgumentNotValidException.class)
      @ResponseStatus(HttpStatus.BAD_REQUEST)
      public UnifyResponse handleMethodArgumentException(HttpServletRequest req, MethodArgumentNotValidException e) {
        StringBuilder messageBuilder = new StringBuilder();
        e.getBindingResult().getFieldErrors().forEach(fieldError -> {
          String field = fieldError.getField();
          String message = fieldError.getDefaultMessage();
          messageBuilder.append(field).append(":").append(message).append(";");
        });
        return new UnifyResponse(ExceptionCode.PARAMS_ERROR, messageBuilder.toString(), this.getRequest(req));
      }
    
      // 获取请求方法和路径
      private String getRequest(HttpServletRequest req) {
        String url = req.getRequestURI();
        String method = req.getMethod();
        return method + " " + url;
      }
    }
    

    ConstraintViolationException通过getConstraintViolations()返回Set<ConstraintViolation<?>>集合,我们使用forEach和StringBuilder遍历和收集相应的信息。

    ConstraintViolationgetPropertyPath().toString()会返回MethodName.Param格式,我们不希望给出MethodName,于是使用replaceAll()将其替换为空。getMessage()获得该Param的校验失败信息。

    MethodArgumentNotValidException通过getFieldErrors()获得List<FieldError>列表,我们同样使用forEach和StringBuilder遍历和收集信息。

    FieldErrorgetField(),getDefaultMessage()获得DTO对象内相应的字段和校验失败信息。

    最终我们将信息打包成UnifyResponse进行返回。

    源代码:https://github.com/PeterWangYong/blog-code/tree/master/handle-exception

  • 相关阅读:
    sourceinsight问题
    mysql函数调用过程
    visual studio 中sstrcpy报错的问题
    mysql基本操作
    c/c++程序连接mysql
    mysql 在visual studio中的配置
    va_start
    c do{}while(0)
    .NET 通用权限设计
    https://zhidao.baidu.com/question/362784520674844572.html
  • 原文地址:https://www.cnblogs.com/Peter2014/p/12834927.html
Copyright © 2011-2022 走看看