8 Spring Boot返回数据及异常统一封装
项目开发中,一般情况下对数据返回的格式可能会有一个统一的要求,一般会包括状态码、信息及数据三部分。举个例子,假设规范要求数据返回的结构如下所示:
{"data":[{"id":5,"userId":5,"name":"test1","articleCount":0}],"errorMessage":"","statusCode":"200"}
其中,data字段存储实际的返回数据;errorMessage存储当出现异常时的异常信息;statusCode存储处理码;一般用一个特殊的码如200来表示无异常;而出现异常时可以存储具体的异常码。
要返回这样的数据,最直接的做法当然是在每一个Controller中去处理,返回的数据本身就封装有处理码、数据、出现异常时的异常信息等字段。这样做导致的问题,就是每一个Controller向外暴露的方法都要创建一个返回的对象来封装这种处理,并在出现异常时捕获异常进行处理。
因此最好是能够统一处理这种转换,这样的话服务提供者就只需关注他原本就需要处理的事情:一是在无异常时返回数据本身;二是在出现异常时抛出合适的异常。
为达到统一处理的目的,需要针对两个场景做单独的处理:一是当无异常时,在原返回的数据基础上封装一层,将状态码等信息包含进来;二是当出现异常时,将异常信息进行封装然后返回给调用方。
1. 执行无异常时返回数据封装
在Spring Boot中,针对返回值的处理是在HandlerAdapter的returnValueHandlers中进行的。我们先尝试创建一个ReturnValueHandler对象实现HandlerMethodReturnValueHandler接口,然后通过WebMvcConfigurer中的addReturnValueHandlers将其添加。如下所示:
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Autowired
private RestReturnValueHandler restReturnValueHandler;
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
handlers.clear();
handlers.add(restReturnValueHandler);
}
}
其中RestReturnValueHanlder定义如下:
/**
* REST类型的返回值处理器
* 将REST的返回结果进行进一步的封装,如原本返回的是data,那么封装后将会是:
* {statusCode: '', errorMessage: '', exception: {}, data: data}
*
* @author LiuQI 2018/5/30 10:48
* @version V1.0
**/
@Component
public class RestReturnValueHandler implements HandlerMethodReturnValueHandler {
@Autowired
private MessageConverter messageConverter;
@Override
public boolean supportsReturnType(MethodParameter returnType) {
if (returnType.hasMethodAnnotation(ResponseBody.class)
|| (!returnType.getDeclaringClass().equals(ModelAndView.class))
&& returnType.getMethod().getDeclaringClass().isAnnotationPresent(RestController.class)) {
return true;
}
return false;
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
mavContainer.setRequestHandled(true);
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("statusCode", STATUS_CODE_SUCCEEDED);
resultMap.put("data", returnValue);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
messageConverter.write(resultMap, MediaType.APPLICATION_JSON_UTF8, new ServletServerHttpResponse(response));
}
private static final String STATUS_CODE_SUCCEEDED = "200";
private static final String STATUS_CODE_INTERNAL_ERROR = "500";
}
然而在测试过程中,发现Rest的请求并未执行这个Handler!最终通过分析源代码,发现Spring Boot本身在RequestMappingHandlerAdapter中注册了一系列的Handler:
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();
// Single-purpose return value types
handlers.add(new ModelAndViewMethodReturnValueHandler());
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
handlers.add(new StreamingResponseBodyReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
this.contentNegotiationManager, this.requestResponseBodyAdvice));
handlers.add(new HttpHeadersReturnValueHandler());
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
// Annotation-based return value types
handlers.add(new ModelAttributeMethodProcessor(false));
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
this.contentNegotiationManager, this.requestResponseBodyAdvice));
// Multi-purpose return value types
handlers.add(new ViewNameMethodReturnValueHandler());
handlers.add(new MapMethodProcessor());
// Custom return value types
if (getCustomReturnValueHandlers() != null) {
handlers.addAll(getCustomReturnValueHandlers());
}
// Catch-all
if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
}
else {
handlers.add(new ModelAttributeMethodProcessor(true));
}
return handlers;
}
其中关键的就是:RequestResponseBodyMethodProcessor,它用于处理REST接口时的返回数据;在添加的时候是先添加这个Processor的,然后才添加getCustomReturnValueHandlers这个方法返回的ValueHandler的。而我们通过WebMvcConfigurer添加进去的ValueHandler是在这个方法里面返回的。而不同的ValueHandler之间又不能通过Order来进行控制,先执行的如果处理过了就不会再执行后续的了。因此只能采取另外的方式进行:一是通过自定义注解的方式;二是通过修改RequestMappingHandlerAdapter中的returnValueHandlers中的值,将RequestResponseBodyMethodProcessor替换成自定义对象。为尽量不变动Controller的开发,此处采用第二种方式进行。
先定义一个RequestResponseBodyMethodProcessor的包装类:
public class HandlerMethodReturnValueHandlerProxy implements HandlerMethodReturnValueHandler {
private HandlerMethodReturnValueHandler proxyObject;
public HandlerMethodReturnValueHandlerProxy(HandlerMethodReturnValueHandler proxyObject) {
this.proxyObject = proxyObject;
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return proxyObject.supportsReturnType(returnType);
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
Map<String, Object> resultMap = new HashMap<>();
resultMap.put("statusCode", STATUS_CODE_SUCCEEDED);
resultMap.put("errorMessage", "");
resultMap.put("data", returnValue);
proxyObject.handleReturnValue(resultMap, returnType, mavContainer, webRequest);
}
private static final String STATUS_CODE_SUCCEEDED = "200";
}
然后通过InitializingBean的方式来修改其属性:
@Configuration
public class RestReturnValueHandlerConfigurer implements InitializingBean {
@Autowired
private RequestMappingHandlerAdapter handlerAdapter;
@Override
public void afterPropertiesSet() throws Exception {
List<HandlerMethodReturnValueHandler> list = handlerAdapter.getReturnValueHandlers();
List<HandlerMethodReturnValueHandler> newList = new ArrayList<>();
if (null != list) {
for (HandlerMethodReturnValueHandler valueHandler: list) {
if (valueHandler instanceof RequestResponseBodyMethodProcessor) {
HandlerMethodReturnValueHandlerProxy proxy = new HandlerMethodReturnValueHandlerProxy(valueHandler);
newList.add(proxy);
} else {
newList.add(valueHandler);
}
}
}
handlerAdapter.setReturnValueHandlers(newList);
}
}
经过这两步就可以了。
2. 执行出现异常时的处理
可以通过ExceptionHandler来进行处理。其实现如下:
@ControllerAdvice
public class ExceptionHandlerAdvice {
/**
* 处理Rest接口请求时的异常
* @param request
* @param response
* @param ex
* @return
*/
@ExceptionHandler(RestException.class)
@ResponseBody
public Map<String, Object> restError(HttpServletRequest request, HttpServletResponse response, Exception ex) {
RestException restException = (RestException) ex;
Map<String, Object> map = new HashMap<>();
map.put("exception", null != restException.getT() ? restException.getT() : restException);
map.put("errorMessage", restException.getMessage());
map.put("url", request.getRequestURL());
map.put("statusCode", restException.getCode());
return map;
}
}
推荐阅读
-
spring boot 2 全局统一返回RESTful风格数据、统一异常处理
-
只需一步,在Spring Boot中统一Restful API返回值格式与统一处理异常
-
spring boot2.x 后端参数校验+统一异常处理+后端自定义全局统一接口返回响应数据格式
-
spring boot 2 全局统一返回RESTful风格数据、统一异常处理
-
8 Spring Boot返回数据及异常统一封装
-
Spring Boot + Nuxt + Debian(六):代码规范(统一返回封装,DTO,BO,枚举,统一异常)
-
spring boot2.x 后端参数校验+统一异常处理+后端自定义全局统一接口返回响应数据格式
-
封装 返回数据 统一异常管理