思路

原理很简单, 就是使用springboot提供的@RestControllerAdvice注解去处理全局的返回值包装以及异常包装, 注意:

  • 该注解的basePackages包含子文件夹, 因此写个顶层包路径即可
  • 架构思路更重要, 应定义两个包
    • 通用实体包, 定义一个工程中统一的顶层枚举, 顶层异常等通用的基础实体, 工程中每个微服务都必须依赖
    • mvc配置包, 定义本章说的全局处理器, 供其他微服务或模块按需引用

实现

如上所说, demo工程结构如下;
demo-cloud

  • demo-common
    • demo-schema 通用实体包
    • demo-mvc-config mvc配置包
  • demo-web
  • demo-search

demo-mvc-config

依赖

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.example</groupId>
  8. <artifactId>demo-schema</artifactId>
  9. </dependency>
  10. <dependency>
  11. <groupId>com.alibaba</groupId>
  12. <artifactId>fastjson</artifactId>
  13. </dependency>
  14. </dependencies>

GlobalResponseWrapper

  • 使用自定义注解来处理一些并不需要包装的方法
  • 注意当返回值为String类型时的默认的jackson转换器会有问题, 解决方案有多种, 可以使用fastjson替换jackson, 此处俺使用最简单的手动处理

    1. /**
    2. * 统一返回值包装
    3. */
    4. @RestControllerAdvice(basePackages = "org.example")
    5. public class GlobalResponseWrapper implements ResponseBodyAdvice<Object> {
    6. @Override
    7. public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
    8. if (methodParameter.getMethodAnnotation(IgnoreReponseWrapper.class) != null
    9. || methodParameter.getMethod().getDeclaringClass().getAnnotation(IgnoreReponseWrapper.class) != null) {
    10. return false;
    11. }
    12. return true;
    13. }
    14. @Override
    15. public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
    16. Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest,
    17. ServerHttpResponse serverHttpResponse) {
    18. if (o instanceof BaseResponse) {
    19. return o;
    20. }
    21. // 解决BaseResponse强转string时, string会匹配StringMessageConverter, 该converter只接收String类型返回值
    22. if (o instanceof String) {
    23. return JSON.toJSONString(BaseResponse.success(o));
    24. }
    25. return BaseResponse.success(o);
    26. }
    27. }

GlobalExceptionWrapper

  • 异常定义, 这里粗略按分层划分的异常, 具体的异常码可以由各个工程详细定义, 各个工程在此基础上也可以附加自身的异常处理器配置
  • 注意spring validation异常信息的获取与处理

    1. /**
    2. * 全局异常处理器
    3. *
    4. * @author xinzhang
    5. * @date 2020/8/14 15:22
    6. */
    7. @ResponseBody
    8. @ResponseStatus(HttpStatus.BAD_REQUEST)
    9. @RestControllerAdvice(basePackages = "org.example")
    10. public class GlobalExceptionWrapper {
    11. private static final String PARAM_ERROR = "请求参数不合法";
    12. private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionWrapper.class);
    13. /**
    14. * 控制层异常
    15. */
    16. @ExceptionHandler(value = RestException.class)
    17. public <T> BaseResponse<T> requestExceptionWrapper(RestException e) {
    18. return BaseResponse.failure(BaseErrorCode.REST_ERROR.value(), e.getMessage());
    19. }
    20. /**
    21. * spring validation参数校验异常
    22. */
    23. @ExceptionHandler(value = MethodArgumentNotValidException.class)
    24. public <T> BaseResponse<T> validateExceptionWrapper(MethodArgumentNotValidException e) {
    25. return BaseResponse.failure(BaseErrorCode.REST_ERROR.value(), e.getBindingResult().getFieldError() != null ?
    26. e.getBindingResult().getFieldError().getDefaultMessage() : PARAM_ERROR);
    27. }
    28. /**
    29. * 业务异常
    30. */
    31. @ExceptionHandler(value = BizException.class)
    32. public <T> BaseResponse<T> bizExceptionWrapper(BizException e) {
    33. return BaseResponse.failure(BaseErrorCode.BIZ_ERROR.value(), e.getMessage());
    34. }
    35. /**
    36. * 未知异常
    37. */
    38. @ExceptionHandler(value = Exception.class)
    39. public <T> BaseResponse<T> exceptionWrapper(Exception e, HttpServletRequest request) {
    40. if (request != null) {
    41. LOGGER.error(String.format("异常URL:%s,ip:%s", request.getRequestURI(), request.getRemoteHost()), e);
    42. } else {
    43. LOGGER.error("系统异常", e);
    44. }
    45. return BaseResponse.failure(BaseErrorCode.UNKNOWN_ERROR.value(), e.getMessage());
    46. }
    47. }

demo-schema

基础实体

  1. /**
  2. * 基础返回实体
  3. */
  4. public class BaseResponse<T> {
  5. private static final String SUCCESS_CODE = "00000";
  6. private static final String SUCCESS_MSG = "success";
  7. /**
  8. * 错误码
  9. */
  10. private String code;
  11. /**
  12. * 错误信息
  13. */
  14. private String msg;
  15. /**
  16. * 请求结果
  17. */
  18. private T data;
  19. public BaseResponse() {
  20. }
  21. public BaseResponse(String code, String msg, T data) {
  22. this.code = code;
  23. this.msg = msg;
  24. this.data = data;
  25. }
  26. public static <T> BaseResponse<T> success(T data) {
  27. return new BaseResponse<>(SUCCESS_CODE, SUCCESS_MSG, data);
  28. }
  29. public static <T> BaseResponse<T> failure(String code, String msg) {
  30. return new BaseResponse<>(code, msg, null);
  31. }
  32. public static <T> BaseResponse<T> failure(BaseEnum errorCode) {
  33. return new BaseResponse<>(errorCode.value(), errorCode.caption(), null);
  34. }
  35. public String getCode() {
  36. return code;
  37. }
  38. public void setCode(String code) {
  39. this.code = code;
  40. }
  41. public String getMsg() {
  42. return msg;
  43. }
  44. public void setMsg(String msg) {
  45. this.msg = msg;
  46. }
  47. public T getData() {
  48. return data;
  49. }
  50. public void setData(T data) {
  51. this.data = data;
  52. }
  53. }

基础枚举

各子工程应定义自己的业务异常码继承BaseEnum

  1. public interface BaseEnum {
  2. /**
  3. * 值
  4. */
  5. String value();
  6. /**
  7. * 注释
  8. */
  9. String caption();
  10. }
  1. public enum BaseErrorCode implements BaseEnum {
  2. UNKNOWN_ERROR("未知异常", "10000"),
  3. REST_ERROR("请求异常", "10001"),
  4. BIZ_ERROR("业务异常", "10002"),
  5. SERVICE_ERROR("服务异常", "10003");
  6. private final String caption;
  7. private final String value;
  8. BaseErrorCode(String caption, String value) {
  9. this.caption = caption;
  10. this.value = value;
  11. }
  12. @Override
  13. public String value() {
  14. return this.value;
  15. }
  16. @Override
  17. public String caption() {
  18. return this.caption;
  19. }
  20. }

基础异常

  1. /**
  2. * 业务异常
  3. *
  4. * @author xinzhang
  5. * @date 2020/8/14 15:05
  6. */
  7. public class BizException extends RuntimeException {
  8. public BizException(BaseEnum baseEnum) {
  9. super(baseEnum.caption());
  10. }
  11. public BizException() {
  12. }
  13. public BizException(String message) {
  14. super(message);
  15. }
  16. public BizException(String message, Throwable cause) {
  17. super(message, cause);
  18. }
  19. public BizException(Throwable cause) {
  20. super(cause);
  21. }
  22. public BizException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
  23. super(message, cause, enableSuppression, writableStackTrace);
  24. }
  25. }
  1. /**
  2. * 请求异常
  3. *
  4. * @author xinzhang
  5. * @date 2020/8/14 15:05
  6. */
  7. public class RestException extends RuntimeException {
  8. public RestException(BaseEnum baseEnum) {
  9. super(baseEnum.caption());
  10. }
  11. public RestException() {
  12. }
  13. public RestException(String message) {
  14. super(message);
  15. }
  16. public RestException(String message, Throwable cause) {
  17. super(message, cause);
  18. }
  19. public RestException(Throwable cause) {
  20. super(cause);
  21. }
  22. public RestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
  23. super(message, cause, enableSuppression, writableStackTrace);
  24. }
  25. }

IgnoreReponseWrapper

用于忽略返回值包装

  1. /**
  2. * 忽略返回值包装, 使用在controller类上或具体的方法上
  3. */
  4. @Target({ElementType.TYPE, ElementType.METHOD})
  5. @Retention(RetentionPolicy.RUNTIME)
  6. public @interface IgnoreReponseWrapper {
  7. }

测试

如此封装后

  • controller层直接返回原Object, 无需手动封装成BaseResponse
  • 业务代码需要抛出异常时, 直接抛出自定义异常(也可以携带自定义异常码), 无需封装成BaseResponse ```java @RestController @RequestMapping(“/test”) public class WebController {

    @GetMapping(“/response”) public String testResponseWrap() {

    1. return "this is a test for response wrapper!";

    }

    @GetMapping(“/exception”) public void testExceptionHandle() {

    1. throw new BizException("this is a test for exception wrapper!");

    } }


  1. {
  2. "code": "00000",
  3. "data": "this is a test for response wrapper!",
  4. "msg": "success"

}

  1. {
  2. "code": "10002",
  3. "msg": "this is a test for exception wrapper!",
  4. "data": null

} ```