思路
原理很简单, 就是使用springboot提供的@RestControllerAdvice注解去处理全局的返回值包装以及异常包装, 注意:
- 该注解的basePackages包含子文件夹, 因此写个顶层包路径即可
- 架构思路更重要, 应定义两个包
- 通用实体包, 定义一个工程中统一的顶层枚举, 顶层异常等通用的基础实体, 工程中每个微服务都必须依赖
- mvc配置包, 定义本章说的全局处理器, 供其他微服务或模块按需引用
实现
如上所说, demo工程结构如下;
demo-cloud
- demo-common
- demo-schema 通用实体包
- demo-mvc-config mvc配置包
- demo-web
- demo-search
- …
demo-mvc-config
依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.example</groupId><artifactId>demo-schema</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId></dependency></dependencies>
GlobalResponseWrapper
- 使用自定义注解来处理一些并不需要包装的方法
注意当返回值为String类型时的默认的jackson转换器会有问题, 解决方案有多种, 可以使用fastjson替换jackson, 此处俺使用最简单的手动处理
/*** 统一返回值包装*/@RestControllerAdvice(basePackages = "org.example")public class GlobalResponseWrapper implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {if (methodParameter.getMethodAnnotation(IgnoreReponseWrapper.class) != null|| methodParameter.getMethod().getDeclaringClass().getAnnotation(IgnoreReponseWrapper.class) != null) {return false;}return true;}@Overridepublic Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest,ServerHttpResponse serverHttpResponse) {if (o instanceof BaseResponse) {return o;}// 解决BaseResponse强转string时, string会匹配StringMessageConverter, 该converter只接收String类型返回值if (o instanceof String) {return JSON.toJSONString(BaseResponse.success(o));}return BaseResponse.success(o);}}
GlobalExceptionWrapper
- 异常定义, 这里粗略按分层划分的异常, 具体的异常码可以由各个工程详细定义, 各个工程在此基础上也可以附加自身的异常处理器配置
注意spring validation异常信息的获取与处理
/*** 全局异常处理器** @author xinzhang* @date 2020/8/14 15:22*/@ResponseBody@ResponseStatus(HttpStatus.BAD_REQUEST)@RestControllerAdvice(basePackages = "org.example")public class GlobalExceptionWrapper {private static final String PARAM_ERROR = "请求参数不合法";private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionWrapper.class);/*** 控制层异常*/@ExceptionHandler(value = RestException.class)public <T> BaseResponse<T> requestExceptionWrapper(RestException e) {return BaseResponse.failure(BaseErrorCode.REST_ERROR.value(), e.getMessage());}/*** spring validation参数校验异常*/@ExceptionHandler(value = MethodArgumentNotValidException.class)public <T> BaseResponse<T> validateExceptionWrapper(MethodArgumentNotValidException e) {return BaseResponse.failure(BaseErrorCode.REST_ERROR.value(), e.getBindingResult().getFieldError() != null ?e.getBindingResult().getFieldError().getDefaultMessage() : PARAM_ERROR);}/*** 业务异常*/@ExceptionHandler(value = BizException.class)public <T> BaseResponse<T> bizExceptionWrapper(BizException e) {return BaseResponse.failure(BaseErrorCode.BIZ_ERROR.value(), e.getMessage());}/*** 未知异常*/@ExceptionHandler(value = Exception.class)public <T> BaseResponse<T> exceptionWrapper(Exception e, HttpServletRequest request) {if (request != null) {LOGGER.error(String.format("异常URL:%s,ip:%s", request.getRequestURI(), request.getRemoteHost()), e);} else {LOGGER.error("系统异常", e);}return BaseResponse.failure(BaseErrorCode.UNKNOWN_ERROR.value(), e.getMessage());}}
demo-schema
基础实体
/*** 基础返回实体*/public class BaseResponse<T> {private static final String SUCCESS_CODE = "00000";private static final String SUCCESS_MSG = "success";/*** 错误码*/private String code;/*** 错误信息*/private String msg;/*** 请求结果*/private T data;public BaseResponse() {}public BaseResponse(String code, String msg, T data) {this.code = code;this.msg = msg;this.data = data;}public static <T> BaseResponse<T> success(T data) {return new BaseResponse<>(SUCCESS_CODE, SUCCESS_MSG, data);}public static <T> BaseResponse<T> failure(String code, String msg) {return new BaseResponse<>(code, msg, null);}public static <T> BaseResponse<T> failure(BaseEnum errorCode) {return new BaseResponse<>(errorCode.value(), errorCode.caption(), null);}public String getCode() {return code;}public void setCode(String code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public T getData() {return data;}public void setData(T data) {this.data = data;}}
基础枚举
各子工程应定义自己的业务异常码继承BaseEnum
public interface BaseEnum {/*** 值*/String value();/*** 注释*/String caption();}
public enum BaseErrorCode implements BaseEnum {UNKNOWN_ERROR("未知异常", "10000"),REST_ERROR("请求异常", "10001"),BIZ_ERROR("业务异常", "10002"),SERVICE_ERROR("服务异常", "10003");private final String caption;private final String value;BaseErrorCode(String caption, String value) {this.caption = caption;this.value = value;}@Overridepublic String value() {return this.value;}@Overridepublic String caption() {return this.caption;}}
基础异常
/*** 业务异常** @author xinzhang* @date 2020/8/14 15:05*/public class BizException extends RuntimeException {public BizException(BaseEnum baseEnum) {super(baseEnum.caption());}public BizException() {}public BizException(String message) {super(message);}public BizException(String message, Throwable cause) {super(message, cause);}public BizException(Throwable cause) {super(cause);}public BizException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}}
/*** 请求异常** @author xinzhang* @date 2020/8/14 15:05*/public class RestException extends RuntimeException {public RestException(BaseEnum baseEnum) {super(baseEnum.caption());}public RestException() {}public RestException(String message) {super(message);}public RestException(String message, Throwable cause) {super(message, cause);}public RestException(Throwable cause) {super(cause);}public RestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {super(message, cause, enableSuppression, writableStackTrace);}}
IgnoreReponseWrapper
用于忽略返回值包装
/*** 忽略返回值包装, 使用在controller类上或具体的方法上*/@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface IgnoreReponseWrapper {}
测试
如此封装后
- controller层直接返回原Object, 无需手动封装成BaseResponse
业务代码需要抛出异常时, 直接抛出自定义异常(也可以携带自定义异常码), 无需封装成BaseResponse ```java @RestController @RequestMapping(“/test”) public class WebController {
@GetMapping(“/response”) public String testResponseWrap() {
return "this is a test for response wrapper!";
}
@GetMapping(“/exception”) public void testExceptionHandle() {
throw new BizException("this is a test for exception wrapper!");
} }
{"code": "00000","data": "this is a test for response wrapper!","msg": "success"
}
{"code": "10002","msg": "this is a test for exception wrapper!","data": null
} ```
