思路
原理很简单, 就是使用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> {
@Override
public 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;
}
@Override
public 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;
}
@Override
public String value() {
return this.value;
}
@Override
public 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
} ```