SpringController返回值和異常自動(dòng)包裝的問(wèn)題小結(jié)
今天遇到一個(gè)需求,在不改動(dòng)原系統(tǒng)代碼的情況下。將Controller的返回值和異常包裝到一個(gè)統(tǒng)一的返回對(duì)象中去。
例如原系統(tǒng)的接口
public String myIp(@ApiIgnore HttpServletRequest request);
返回的只是一個(gè)IP字符串"0:0:0:0:0:0:0:1",目前接口需要包裝為:
{"code":200,"message":"","result":"0:0:0:0:0:0:0:1","success":true}
而原異常跳轉(zhuǎn)到error頁(yè)面,需要調(diào)整為
{ "success": false, "message": "For input string: \"fdsafddfs\"", "code": 500, "result": "message" }
因此就有了2個(gè)工作子項(xiàng)需要完成:
1)Exception的處理
2)controller return值的處理
Exception的自動(dòng)包裝
返回的exception處理可以采用@RestControllerAdvice來(lái)處理。
建立自己的Advice類(lèi),注入國(guó)際化資源(異常需要支持多語(yǔ)言)
package org.ccframe.commons.mvc; import lombok.extern.log4j.Log4j2; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; import org.ccframe.commons.filter.CcRequestLoggingFilter; import org.ccframe.commons.util.BusinessException; import org.ccframe.config.GlobalEx; import org.ccframe.subsys.core.dto.Result; import org.springframework.context.MessageSource; import org.springframework.context.NoSuchMessageException; import org.springframework.core.MethodParameter; import org.springframework.http.ResponseEntity; import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.NoHandlerFoundException; import javax.servlet.http.HttpServletRequest; import java.util.Locale; @RestControllerAdvice @Log4j2 public class GlobalRestControllerAdvice{ private MessageSource messageSource; //國(guó)際化資源 private LocaleResolver localeResolver; private Object[] EMPTY_ARGS = new Object[0]; public GlobalRestControllerAdvice(MessageSource messageSource, LocaleResolver localeResolver){ this.messageSource = messageSource; this.localeResolver = localeResolver; } private Result<String> createError(HttpServletRequest request, Exception e,int code, String msgKey, Object[] args){ Locale currentLocale = localeResolver.resolveLocale(request); String message = ""; try { message = messageSource.getMessage(msgKey, args, currentLocale); }catch (NoSuchMessageException ex){ message = e.getMessage(); }finally { log.error(message); CcRequestLoggingFilter.pendingLog(); //服務(wù)器可以記錄出錯(cuò)時(shí)的請(qǐng)求啦?? } return Result.error(code, message, msgKey); } @ExceptionHandler(NoHandlerFoundException.class) public Result<?> handlerNoFoundException(HttpServletRequest request, Exception e) { return createError(request, e, HttpStatus.SC_NOT_FOUND, "error.mvc.uriNotFound", EMPTY_ARGS); } @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public Result<?> httpRequestMethodNotSupportedException(HttpServletRequest request, HttpRequestMethodNotSupportedException e){ return createError(request,e, HttpStatus.SC_NOT_FOUND,"error.mvc.methodNotSupported", new Object[]{e.getMethod(), StringUtils.join(e.getSupportedMethods(), GlobalEx.DEFAULT_TEXT_SPLIT_CHAR)}); } @ExceptionHandler(BusinessException.class) public Result<?> businessException(HttpServletRequest request, BusinessException e){ return createError(request,e, HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMsgKey(), e.getArgs()); } @ExceptionHandler(ObjectOptimisticLockingFailureException.class) //樂(lè)觀鎖異常 public Result<?> objectOptimisticLockingFailureException(HttpServletRequest request, ObjectOptimisticLockingFailureException e){ return createError(request,e, HttpStatus.SC_INTERNAL_SERVER_ERROR, "errors.db.optimisticLock", EMPTY_ARGS); } @ExceptionHandler(MaxUploadSizeExceededException.class) // 文件上傳超限,nginx請(qǐng)?jiān)O(shè)置為10M public Result<?> handleMaxUploadSizeExceededException(HttpServletRequest request, MaxUploadSizeExceededException e) { return createError(request, e, HttpStatus.SC_INTERNAL_SERVER_ERROR, "error.mvc.fileTooLarge", EMPTY_ARGS); } @ExceptionHandler(Exception.class) @ResponseBody public Result<?> handleException(HttpServletRequest request, Exception e) { log.error(e); return createError(request,e, HttpStatus.SC_INTERNAL_SERVER_ERROR, "message", new Object[]{e.getMessage()}); } }
在Config類(lèi)初始化該Bean(當(dāng)然也可以使用@Component支持掃描,隨你喜歡)
@Bean public GlobalRestControllerAdvice globalRestControllerAdvice(MessageSource messageSource, LocaleResolver localeResolver){ return new GlobalRestControllerAdvice(messageSource, localeResolver); }
controller return值的自動(dòng)包裝
網(wǎng)上的例子有很多坑,包括使用HandlerMethodReturnValueHandler,看了源碼才發(fā)現(xiàn)。還是ResponseBodyAdvice好使。
建立自己的處理Bean
package org.ccframe.commons.mvc; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.TypeReference; import org.apache.http.HttpStatus; import org.ccframe.commons.util.JsonUtil; import org.ccframe.subsys.core.dto.Result; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import springfox.documentation.swagger.web.ApiResourceController; import java.util.regex.Pattern; @ControllerAdvice public class CcResponseBodyAdvice implements ResponseBodyAdvice<Object> { private static final Pattern CONTROLLER_PATTERN = Pattern.compile("^org\\.ccframe\\.(subsys|sdk)\\.[a-z0-9]+\\.controller\\."); @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return // 只有自己的cotroller類(lèi)才需要進(jìn)入,否則swagger都會(huì)掛了 CONTROLLER_PATTERN.matcher(returnType.getContainingClass().getName()).find(); } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { System.out.println(returnType.getContainingClass()); Result<Object> result = new Result<>(); result.setResult(body); result.setCode(HttpStatus.SC_OK); if(body instanceof String){ //String返回要特殊處理 return JSON.toJSONString(result); }else { return result; } } }
如果你不需要根據(jù)正則來(lái)指定包,可以直接用RestControllerAdvice的basePackages屬性來(lái)過(guò)濾
注意這里有2個(gè)坑
1)String類(lèi)型的返回被其它的轉(zhuǎn)換接口StringHttpMessageConverter處理,因此返回要進(jìn)行JSON編碼而不能返回其他類(lèi)型,否則會(huì)報(bào)cast類(lèi)型錯(cuò),因此就有了String部分的特殊處理方法。
2)controller方法簽名返回是void時(shí),不會(huì)被處理。為什么,有什么辦法?得看spring這段源碼:
當(dāng)returnValue==null時(shí),設(shè)置為RequestHandled,也就是提前結(jié)束了。后面任何返回的處理都不再進(jìn)行。所以,如果一定要返回null值的話(huà),可以在controller里返回一個(gè)
return new ResponseEntity<Void>(HttpStatus.OK);
這樣在返回的值里面就有詳細(xì)的結(jié)構(gòu)了。
最后要生效的話(huà),在Config類(lèi)初始它:
@Bean public CcResponseBodyAdvice ccResponseBodyAdvice() { return new CcResponseBodyAdvice(); }
最后。上面兩個(gè)Bean也可以寫(xiě)在一個(gè),有興趣的自己嘗試。
---------------
null無(wú)法被BodyAdvice處理的問(wèn)題。隨著源碼跟蹤,慢慢知道怎么回事了,我們嘗試根本來(lái)解決這個(gè)問(wèn)題。從這個(gè)圖開(kāi)始:
當(dāng)返回為null時(shí),mavContainer.isRequestHandled()為true導(dǎo)致了后面的沒(méi)有處理。
那么想當(dāng)然的,mavContainer.isRequestHandled()為flase不久解決了嗎,向前跟蹤,基本到MVC invoke的核心代碼里了,發(fā)現(xiàn)在invoke前,mavContainer.isRequestHandled()變成了true,再繼續(xù)跟蹤,找到這個(gè)方法:
在HandlerMethodArgumentResolverComposite的argumentResolvers看到了上面這個(gè)。進(jìn)行了setRequestHandled。HandlerMethodArgumentResolver是spring controller的參數(shù)自動(dòng)注入機(jī)制??戳讼略创a也沒(méi)有太多的擴(kuò)展點(diǎn),于是只能換個(gè)思路。
到此這篇關(guān)于SpringController返回值和異常自動(dòng)包裝的文章就介紹到這了,更多相關(guān)SpringController返回值內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中Socket設(shè)置連接超時(shí)的代碼分享
在我們?nèi)粘_B接中,如果超時(shí)時(shí)長(zhǎng)過(guò)長(zhǎng)的話(huà),在開(kāi)發(fā)時(shí)會(huì)影響測(cè)試,下面這篇文章主要給大家分享了關(guān)于Java中Socket設(shè)置連接超時(shí)的代碼,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-06-06Jenkins遠(yuǎn)程部署war包過(guò)程圖解
這篇文章主要介紹了Jenkins遠(yuǎn)程部署war包過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05JUnit測(cè)試控制@Test執(zhí)行順序的三種方式小結(jié)
這篇文章主要介紹了JUnit測(cè)試控制@Test執(zhí)行順序的三種方式小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09Java如何將Excel數(shù)據(jù)導(dǎo)入到數(shù)據(jù)庫(kù)
這篇文章主要為大家詳細(xì)介紹了Java將Excel數(shù)據(jù)導(dǎo)入到數(shù)據(jù)庫(kù)的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10Java中==與equals()及hashcode()三者之間的關(guān)系詳解
最近也是在讀Hollis的《深入理解Java核心技術(shù)》里面一節(jié)講到了equals()和hashcode()的關(guān)系,對(duì)于這個(gè)高頻面試點(diǎn),咱們需要認(rèn)真理清一下幾者之間的關(guān)系2022-10-10Spring的Aware接口實(shí)現(xiàn)及執(zhí)行順序詳解
這篇文章主要為大家介紹了Spring的Aware接口實(shí)現(xiàn)及執(zhí)行順序詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Java和Python現(xiàn)在都挺火,我應(yīng)該怎么選?
這篇文章主要介紹了Java和Python現(xiàn)在都挺火,我應(yīng)該怎么選?本文通過(guò)全面分析給大家做個(gè)參考,需要的朋友可以參考下2020-07-07mybatis?like模糊查詢(xún)特殊字符報(bào)錯(cuò)轉(zhuǎn)義處理方式
這篇文章主要介紹了mybatis?like模糊查詢(xún)特殊字符報(bào)錯(cuò)轉(zhuǎn)義處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01