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 統一異常處理Spring Boot
- spring-boot 統一異常捕獲Springboot
- Spring Boot 異常處理Spring Boot
- Spring Boot統一異常處理最佳實踐Spring Boot
- spring boot配置跨域、全域性異常處理、page分頁配置、統一返回MessageResultSpring Boot跨域
- Spring Boot實戰系列(4)統一異常處理Spring Boot
- 只需一步,在Spring Boot中統一Restful API返回值格式與統一處理異常Spring BootRESTAPI
- Spring Boot統一異常處理以及引數校驗Spring Boot
- Spring Boot乾貨系列:(十三)Spring Boot全域性異常處理整理Spring Boot
- 優雅的處理Spring Boot異常資訊Spring Boot
- Spring Boot優雅地處理404異常Spring Boot
- Laravel 8 表單驗證丟擲異常返回 json 格式資料LaravelJSON
- 統一返回物件和異常處理(二)物件
- 統一返回物件和異常處理(一)物件
- Spring Boot 2 Webflux的全域性異常處理Spring BootWebUX
- spring-boot-route(四)全域性異常處理Springboot
- 知識點-Spring Boot 異常處理彙總Spring Boot
- 【spring boot】【spring cloud】異常:找不到方法HikariDataSource.getMetricsTrackerFactory()Spring BootCloud
- Spring Boot2從入門到實戰:統一異常處理Spring Boot
- Spring Boot學習之No bean named 'entityManagerFactory' available異常Spring BootBeanAI
- Spring MVC統一異常處理SpringMVC
- springboot統一異常處理及返回資料的處理Spring Boot
- Spring Boot 中關於自定義異常處理的套路!Spring Boot
- Spring Cloud Gateway過濾器精確控制異常返回(分析篇)SpringCloudGateway過濾器
- Spring Cloud Gateway過濾器精確控制異常返回(實戰,完全定製返回body)SpringCloudGateway過濾器
- 一站式統一返回值封裝、異常處理、異常錯誤碼解決方案—最強的Sping Boot介面優雅響應處理器封裝boot
- spring中的統一異常處理Spring
- 29.Spring Boot中異常處理與REST格式處理Spring BootREST
- 從零開始學Spring Boot系列-返回json資料Spring BootJSON
- ?laravel8 中介軟體對處理返回結果或異常進行格式統一處理Laravel
- Spring Boot 2.x(六):優雅的統一返回值Spring Boot
- spring boot - mybatis Map集合返回空欄位Spring BootMyBatis
- Spring Boot返回靜態錯誤頁面Spring Boot
- Spring Boot 安裝Spring Boot
- spring boot 實現監聽器、過濾器、全域性異常處理Spring Boot過濾器
- 實戰Spring Boot 2.0系列(二) - 全域性異常處理和測試Spring Boot
- 執行程式時,程式返回TooManyResultsException異常行程OOMException