Springboot-优雅装备接口呼应和一致反常处理

一、前语

越来越多的项目开端根据前后端分离的形式进行开发,这对后端接口的报文格局便有了一定的要求。一般,咱们会采用JSON格局作为前后端交流数据格局,然后减少沟通本钱等。

@PostMapping("/user/add")
public ResponseInfo addUser(@RequestBody @Validated User user) {
    userService.add(user);
    return ResponseInfo.success();
}

咱们看看这个代码,有什么问题。 咱们回来了一致的封装成果集ResponseInfo 但是后面一切的controller 都这么写不太友好。

  1. 回来内容这么不够清晰详细
  2. 一切controller 这么写添加重复工作量

咱们能够这么去优化:

Spirng 供给了 ResponseBodyAdvice 接口,支持在音讯转化器执行转化之前,对接口的回来成果进行处理,再结合 @ControllerAdvice 注解即可轻松支持上述功用。

二、一致呼应装备

准备文件:

ResponseCodeEnums.java 呼应类型枚举类

ResponseInfo.java 呼应数据封装类

ResponseResultHandler.java 呼应信息增强类

开端

ResponseCodeEnums.java

@Getter
@AllArgsConstructor
public enum ResponseCodeEnums {
    SUCCESS(200, "success"),
    FAIL(500, "failed"),
    HTTP_STATUS_200(200, "ok"),
    HTTP_STATUS_400(400, "request error"),
    HTTP_STATUS_401(401, "no authentication"),
    HTTP_STATUS_403(403, "no authorities"),
    HTTP_STATUS_500(500, "server error");
    private final int code;
    private final String message;
}

ResponseInfo.java

@Data
public class ResponseInfo<T> {
    /**
     * 状况码
     */
    protected int code;
    /**
     * 呼应信息
     */
    protected String message;
    /**
     * 回来数据
     */
    private T data;
    public static <T> ResponseInfo<T> success() {
        return new ResponseInfo<>();
    }
    public static <T> ResponseInfo<T> success(T data) {
        return new ResponseInfo<>(data);
    }
    public static <T> ResponseInfo<T> fail(String message) {
        return new ResponseInfo<>(ResponseCodeEnums.FAIL.getCode(), message);
    }
    public ResponseInfo() {
        this.code = ResponseCodeEnums.SUCCESS.getCode();
        this.message = ResponseCodeEnums.SUCCESS.getMessage();
    }
    public ResponseInfo(ResponseCodeEnums statusEnums) {
        this.code = statusEnums.getCode();
        this.message = statusEnums.getMessage();
    }
    /**
     * 若没有数据回来,能够人为指定状况码和提示信息
     */
    public ResponseInfo(int code, String msg) {
        this.code = code;
        this.message = msg;
    }
    /**
     * 有数据回来时,状况码为200,默许提示信息为“操作成功!”
     */
    public ResponseInfo(T data) {
        this.data = data;
        this.code = ResponseCodeEnums.SUCCESS.getCode();
        this.message = ResponseCodeEnums.SUCCESS.getMessage();
    }
    /**
     * 有数据回来,状况码为 200,人为指定提示信息
     */
    public ResponseInfo(T data, String msg) {
        this.data = data;
        this.code = ResponseCodeEnums.SUCCESS.getCode();
        this.message = msg;
    }
}

ResponseResultHandler.java

@Slf4j
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        log.info("supports:{}", returnType.getDeclaringClass().getName());
        /**
         * 扫除swagger-ui恳求回来数据增强
         */
        return !returnType.getDeclaringClass().getName().contains("springfox");
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 如果是ResponseInfo数据类型就直接回来
        if (body instanceof ResponseInfo) {
            return body;
        }
        // 如果是空,则回来成功
        else if (body == null) {
            return ResponseInfo.success();
        } 
        // 如果是反常类型就直接回来
        else if (body instanceof Exception) {
            return body;
        } 
        // 如果是String类型则直接回来String类型
        else if (body instanceof String) {
            return body;
        } 
        // 回来封装后的数据
        else {
            return ResponseInfo.success(body);
        }
    }
}

测验

@RestController
public class TestController {
    @GetMapping("/test")
    public void hello(){}
}

成果

{
  "code": 200,
  "message": "success",
  "data": null
}

OK,成功地呼应了成果,到此装备完结,下面开端装备一致反常处理。

三、全局反常装备

在日常开发中,咱们经过try…catch的形式捕获反常,并进行处理。在SpringBoot中,咱们能够经过RestControllerAdvice注解来界说全局反常处理,这样就无需每处都try…catch了。

示例:

@Slf4j
@RestControllerAdvice
public class ExceptionHandlerAdvice {
​
  /**
   * 参数格局反常处理
   */
  @ExceptionHandler({IllegalArgumentException.class})
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public ResponseInfo<String> badRequestException(IllegalArgumentException ex) {
    log.error("参数格局不合法:{}", ex.getMessage());
    return new ResponseInfo<>(HttpStatus.BAD_REQUEST.value() + "", "参数格局不符!");
  }
​
  /**
   * 权限不足反常处理
   */
  @ExceptionHandler({AccessDeniedException.class})
  @ResponseStatus(HttpStatus.FORBIDDEN)
  public ResponseInfo<String> badRequestException(AccessDeniedException ex) {
    return new ResponseInfo<>(HttpStatus.FORBIDDEN.value() + "", ex.getMessage());
  }
​
  /**
   * 参数缺失反常处理
   */
  @ExceptionHandler({MissingServletRequestParameterException.class})
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public ResponseInfo<String> badRequestException(Exception ex) {
    return new ResponseInfo<>(HttpStatus.BAD_REQUEST.value() + "", "缺少必填参数!");
  }
​
  /**
   * 空指针反常
   */
  @ExceptionHandler(NullPointerException.class)
  @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
  public ResponseInfo<String> handleTypeMismatchException(NullPointerException ex) {
    log.error("空指针反常,{}", ex.getMessage());
    return ResponseInfo.fail("空指针反常");
  }
​
  @ExceptionHandler(Exception.class)
  @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
  public ResponseInfo<String> handleUnexpectedServer(Exception ex) {
    log.error("体系反常:", ex);
    return ResponseInfo.fail("体系产生反常,请联系管理员");
  }
​
  /**
   * 体系反常处理
   */
  @ExceptionHandler(Throwable.class)
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  public ResponseInfo<String> exception(Throwable throwable) {
    log.error("体系反常", throwable);
    return new ResponseInfo<>(HttpStatus.INTERNAL_SERVER_ERROR.value() + "体系反常,请联系管理员!");
  }
}

在上述办法中,对一些常见的反常进行了一致处理。一般情况下,根据事务需要还会界说事务反常,并对事务反常进行处理,我们能够根据自己项目中反常的运用情况进行拓宽。

关于@RestControllerAdvice的几点阐明:

  • @RestControllerAdvice注解包含了@Component注解,会把被注解的类作为组件交给Spring来管理。
  • @RestControllerAdvice注解包含了@ResponseBody注解,反常处理完之后给调用方输出一个JSON格局的封装数据。
  • @RestControllerAdvice注解有一个basePackages属性,该属性用来阻拦哪个包中的反常信息,一般不指定,阻拦项目工程中的一切反常。
  • 在办法上经过@ExceptionHandler注解来指定详细的反常,在办法中处理该反常信息,最终将成果经过一致的JSON结构体回来给调用者。

测验:

@RestController
public class TestController {
    @GetMapping("/test")
    public void hello(){
        int i = 1 / 0;
    }
}

成果:

{
  "code": 500,
  "message": "体系产生反常,请联系管理员",
  "data": null
}

能够看到一致反常处理对空指针反常进行了阻拦处理,并回来了ExceptionHandlerAdvice中界说的一致报文格局。

四、总结

在运用SpringBoot或其他项目中,一致的报文格局和一致的反常处理都是有必要的。本篇文章介绍了根据SpringBoot的完成,如果你的项目中采用了其他的技能栈,则可考虑对应的处理方式。同时,日常中许多类似的功用都能够一致进行处理,防止大量无效的硬编码,提高工作效率。