Spring Boot 無(wú)侵入式 實(shí)現(xiàn)API接口統(tǒng)一JSON格式返回
? ? ?
? ?正文? ?
無(wú)侵入式 統(tǒng)一返回JSON格式
其實(shí)本沒(méi)有沒(méi)打算寫這篇博客的,但還是要寫一下寫這篇博客的起因是因?yàn)椋F(xiàn)在呆著的這家公司居然沒(méi)有統(tǒng)一的API返回格式?,詢問(wèn)主管他居然告訴我用HTTP狀態(tài)碼就夠用了(fxxk),天哪HTTP狀態(tài)碼真的夠用嗎?
在仔細(xì)的閱讀了項(xiàng)目源碼后發(fā)現(xiàn),在API請(qǐng)求的是居然沒(méi)有業(yè)務(wù)異常(黑人問(wèn)好)。好吧 居然入坑了只能遵照項(xiàng)目風(fēng)格了,懶得吐槽了。
因?yàn)轫?xiàng)目已經(jīng)開發(fā)了半年多了, 要是全部接口都做修改工作量還是挺大的, 只能用這種無(wú)侵入式的方案來(lái)解決.
項(xiàng)目源代碼: https://github.com/469753862/galaxy-blogs/tree/master/code/responseResult
定義JSON格式
定義返回JSON格式
后端返回給前端一般情況下使用JSON格式, 定義如下
{
????"code":?200,
????"message":?"OK",
????"data":?{
????}
}
code: 返回狀態(tài)碼 message: 返回信息的描述 data: 返回值
定義JavaBean字段
定義狀態(tài)碼枚舉類
@ToString
@Getter
public?enum?ResultStatus?{
????SUCCESS(HttpStatus.OK,?200,?"OK"),
????BAD_REQUEST(HttpStatus.BAD_REQUEST,?400,?"Bad?Request"),
????INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR,?500,?"Internal?Server?Error"),;
????/**?返回的HTTP狀態(tài)碼,??符合http請(qǐng)求?*/
????private?HttpStatus?httpStatus;
????/**?業(yè)務(wù)異常碼?*/
????private?Integer?code;
????/**?業(yè)務(wù)異常信息描述?*/
????private?String?message;
????ResultStatus(HttpStatus?httpStatus,?Integer?code,?String?message)?{
????????this.httpStatus?=?httpStatus;
????????this.code?=?code;
????????this.message?=?message;
????}
}
狀態(tài)碼和信息以及http狀態(tài)碼就能一一對(duì)應(yīng)了便于維護(hù), 有同學(xué)有疑問(wèn)了為什么要用到http狀態(tài)碼呀,因?yàn)槲乙嫒蓓?xiàng)目以前的代碼, 沒(méi)有其他原因, 當(dāng)然其他同學(xué)不喜歡http狀態(tài)碼的可以吧源碼中HttpStatus給刪除了
定義返回體類
@Getter
@ToString
public?class?Result<T>?{
????/**?業(yè)務(wù)錯(cuò)誤碼?*/
????private?Integer?code;
????/**?信息描述?*/
????private?String?message;
????/**?返回參數(shù)?*/
????private?T?data;
????private?Result(ResultStatus?resultStatus,?T?data)?{
????????this.code?=?resultStatus.getCode();
????????this.message?=?resultStatus.getMessage();
????????this.data?=?data;
????}
????/**?業(yè)務(wù)成功返回業(yè)務(wù)代碼和描述信息?*/
????public?static?Result?success()? {
????????return?new?Result(ResultStatus.SUCCESS,?null);
????}
????/**?業(yè)務(wù)成功返回業(yè)務(wù)代碼,描述和返回的參數(shù)?*/
????public?static??Result?success(T?data)? {
????????return?new?Result(ResultStatus.SUCCESS,?data);
????}
????/**?業(yè)務(wù)成功返回業(yè)務(wù)代碼,描述和返回的參數(shù)?*/
????public?static??Result?success(ResultStatus?resultStatus,?T?data)? {
????????if?(resultStatus?==?null)?{
????????????return?success(data);
????????}
????????return?new?Result(resultStatus,?data);
????}
????/**?業(yè)務(wù)異常返回業(yè)務(wù)代碼和描述信息?*/
????public?static??Result?failure()? {
????????return?new?Result(ResultStatus.INTERNAL_SERVER_ERROR,?null);
????}
????/**?業(yè)務(wù)異常返回業(yè)務(wù)代碼,描述和返回的參數(shù)?*/
????public?static??Result?failure(ResultStatus?resultStatus)? {
????????return?failure(resultStatus,?null);
????}
????/**?業(yè)務(wù)異常返回業(yè)務(wù)代碼,描述和返回的參數(shù)?*/
????public?static??Result?failure(ResultStatus?resultStatus,?T?data)? {
????????if?(resultStatus?==?null)?{
????????????return?new?Result(ResultStatus.INTERNAL_SERVER_ERROR,?null);
????????}
????????return?new?Result(resultStatus,?data);
????}
}
因?yàn)槭褂脴?gòu)造方法進(jìn)行創(chuàng)建對(duì)象太麻煩了, 我們使用靜態(tài)方法來(lái)創(chuàng)建對(duì)象這樣簡(jiǎn)單明了
Result實(shí)體返回測(cè)試
@RestController
@RequestMapping("/hello")
public?class?HelloController?{
????private?static?final?HashMap?INFO;
????static?{
????????INFO?=?new?HashMap<>();
????????INFO.put("name",?"galaxy");
????????INFO.put("age",?"70");
????}
????@GetMapping("/hello")
????public?Map?hello()? {
????????return?INFO;
????}
????@GetMapping("/result")
????@ResponseBody
????public?Result 到這里我們已經(jīng)簡(jiǎn)單的實(shí)現(xiàn)了統(tǒng)一JSON格式了, 但是我們也發(fā)現(xiàn)了一個(gè)問(wèn)題了,想要返回統(tǒng)一的JSON格式需要返回Result才可以, 我明明返回Object可以了, 為什么要重復(fù)勞動(dòng), 有沒(méi)有解決方法, 當(dāng)然是有的啦, 下面我們開始優(yōu)化我們的代碼吧
統(tǒng)一返回JSON格式進(jìn)階-全局處理(@RestControllerAdvice)
我?guī)煾到?jīng)常告訴我的一句話: “你就是一個(gè)小屁孩, 你遇到的問(wèn)題都已經(jīng)不知道有多少人遇到過(guò)了, 你會(huì)想到的問(wèn)題, 已經(jīng)有前輩想到過(guò)了. 你準(zhǔn)備解決的問(wèn)題, 已經(jīng)有人把坑填了”。是不是很雞湯, 是不是很勵(lì)志, 讓我對(duì)前輩們充滿著崇拜, 事實(shí)上他對(duì)我說(shuō)的是: “自己去百度”, 這五個(gè)大字, 其實(shí)這五個(gè)大字已經(jīng)說(shuō)明上明的B話了, 通過(guò)不斷的百度和Google發(fā)現(xiàn)了很多的解決方案.
我們都知道使用@ResponseBody注解會(huì)把返回Object序列化成JSON字符串,就先從這個(gè)入手吧, 大致就是在序列化前把Object賦值給Result就可以了, 大家可以觀摩org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice和org.springframework.web.bind.annotation.ResponseBody
@ResponseBody繼承類
我們已經(jīng)決定從@ResponseBody注解入手了就創(chuàng)建一個(gè)注解類繼承@ResponseBody, 很干凈什么都沒(méi)有哈哈,@ResponseResultBody 可以標(biāo)記在類和方法上這樣我們就可以跟自由的進(jìn)行使用了
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,?ElementType.METHOD})
@Documented
@ResponseBody
public?@interface?ResponseResultBody?{
}
ResponseBodyAdvice繼承類
@RestControllerAdvice
public?class?ResponseResultBodyAdvice?implements?ResponseBodyAdvice<Object>?{
????private?static?final?Class?extends?Annotation>?ANNOTATION_TYPE?=?ResponseResultBody.class;
????/**
?????*?判斷類或者方法是否使用了?@ResponseResultBody
?????*/
????@Override
????public?boolean?supports(MethodParameter?returnType,?Class?extends?HttpMessageConverter>>?converterType)?{
????????return?AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(),?ANNOTATION_TYPE)?||?returnType.hasMethodAnnotation(ANNOTATION_TYPE);
????}
????/**
?????*?當(dāng)類或者方法使用了?@ResponseResultBody?就會(huì)調(diào)用這個(gè)方法
?????*/
????@Override
????public?Object?beforeBodyWrite(Object?body,?MethodParameter?returnType,?MediaType?selectedContentType,?Class?extends?HttpMessageConverter>>?selectedConverterType,?ServerHttpRequest?request,?ServerHttpResponse?response)?{
????????//?防止重復(fù)包裹的問(wèn)題出現(xiàn)
????????if?(body?instanceof?Result)?{
????????????return?body;
????????}
????????return?Result.success(body);
????}
}
RestControllerAdvice返回測(cè)試
@RestController
@RequestMapping("/helloResult")
@ResponseResultBody
public?class?HelloResultController?{
????private?static?final?HashMap?INFO;
????static?{
????????INFO?=?new?HashMap();
????????INFO.put("name",?"galaxy");
????????INFO.put("age",?"70");
????}
????@GetMapping("hello")
????public?HashMap?hello()? {
????????return?INFO;
????}
????/**?測(cè)試重復(fù)包裹?*/
????@GetMapping("result")
????public?Result 是不是很神奇, 直接返回Object就可以統(tǒng)一JSON格式了, 就不用每個(gè)返回都返回Result對(duì)象了,直接讓SpringMVC幫助我們進(jìn)行統(tǒng)一的管理, 簡(jiǎn)直完美
只想看接口哦, helloError和helloMyError是會(huì)直接拋出異常的接口,我好像沒(méi)有對(duì)異常返回進(jìn)行統(tǒng)一的處理哦
統(tǒng)一返回JSON格式進(jìn)階-異常處理(@ExceptionHandler))
臥槽, 異常處理, 差點(diǎn)把這茬給忘了, 這個(gè)異常處理就有很多方法了,先看看我?guī)煾档奶幚矸绞? 我剛拿到這個(gè)代碼的時(shí)候很想吐槽, 對(duì)異常類的處理這么殘暴的嗎, 直接用PrintWriter直接輸出結(jié)果, 果然是老師傅, 我要是有100個(gè)異常類, 不得要寫100個(gè) if else了. 趕緊改改睡吧
@Configuration
public?class?MyExceptionHandler?implements?HandlerExceptionResolver?{
????public?ModelAndView?resolveException(HttpServletRequest?request,?HttpServletResponse?response,
?????????????????????????????????????????Object?handler,?Exception?ex)?{
????????PrintWriter?out?=?getPrintWrite(response);
????????if?(ex?instanceof?XXXException)?{
????????????out.write(JsonUtil.formatJson(ResultEnum.PAY_ERROR.getCode(),?ex.getMessage()));
????????}?else?{
????????????out.write(JsonUtil.formatJson(ResultEnum.FAIL.getCode(),?"服務(wù)器異常"));
????????}
????????if?(null?!=?out)?{
????????????out.close();
????????}
????????return?mav;
????}
????private?PrintWriter?getPrintWrite(HttpServletResponse?response)?{
????????PrintWriter?out?=?null;
????????try?{
????????????response.setHeader("Content-type",?"text/html;charset=UTF-8");
????????????response.setCharacterEncoding("UTF-8");
????????????out?=?response.getWriter();
????????}?catch?(IOException?e)?{
????????????log.error("PrintWriter?is?exception",?e);
????????}
????????return?out;
????}
}
上面的代碼看看還是沒(méi)有問(wèn)題的, 別學(xué)過(guò)去哦,
異常處理@ResponseStatus(不推薦)
@ResponseStatus用法如下,可用在Controller類和Controller方法上以及Exception類上但是這樣的工作量還是挺大的
@RestController
@RequestMapping("/error")
@ResponseStatus(value?=?HttpStatus.INTERNAL_SERVER_ERROR,?reason?=?"Java的異常")
public?class?HelloExceptionController?{
????private?static?final?HashMap?INFO;
????static?{
????????INFO?=?new?HashMap();
????????INFO.put("name",?"galaxy");
????????INFO.put("age",?"70");
????}
????@GetMapping()
????public?HashMap?helloError()?throws?Exception? {
????????throw?new?Exception("helloError");
????}
????@GetMapping("helloJavaError")
????@ResponseStatus(value?=?HttpStatus.INTERNAL_SERVER_ERROR,?reason?=?"Java的異常")
????public?HashMap?helloJavaError()?throws?Exception? {
????????throw?new?Exception("helloError");
????}
????@GetMapping("helloMyError")
????public?HashMap?helloMyError()?throws?Exception? {
????????throw?new?MyException();
????}
}
@ResponseStatus(value?=?HttpStatus.INTERNAL_SERVER_ERROR,?reason?=?"自己定義的異常")
class?MyException?extends?Exception?{
}
全局異常處理@ExceptionHandler(推薦)
把ResponseResultBodyAdvice類進(jìn)行改造一下,代碼有點(diǎn)多了
主要參考了org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler#handleException()方法, 有空可以看一下
@Slf4j
@RestControllerAdvice
public?class?ResponseResultBodyAdvice?implements?ResponseBodyAdvice<Object>?{
????private?static?final?Class?extends?Annotation>?ANNOTATION_TYPE?=?ResponseResultBody.class;
????/**?判斷類或者方法是否使用了?@ResponseResultBody?*/
????@Override
????public?boolean?supports(MethodParameter?returnType,?Class?extends?HttpMessageConverter>>?converterType)?{
????????return?AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(),?ANNOTATION_TYPE)?||?returnType.hasMethodAnnotation(ANNOTATION_TYPE);
????}
????/**?當(dāng)類或者方法使用了?@ResponseResultBody?就會(huì)調(diào)用這個(gè)方法?*/
????@Override
????public?Object?beforeBodyWrite(Object?body,?MethodParameter?returnType,?MediaType?selectedContentType,?Class?extends?HttpMessageConverter>>?selectedConverterType,?ServerHttpRequest?request,?ServerHttpResponse?response)?{
????????if?(body?instanceof?Result)?{
????????????return?body;
????????}
????????return?Result.success(body);
????}
????/**
?????*?提供對(duì)標(biāo)準(zhǔn)Spring?MVC異常的處理
?????*
?????*?@param?ex??????the?target?exception
?????*?@param?request?the?current?request
?????*/
????@ExceptionHandler(Exception.class)
????public?final?ResponseEntity>?exceptionHandler(Exception?ex,?WebRequest?request)?{
????????log.error("ExceptionHandler:?{}",?ex.getMessage());
????????HttpHeaders?headers?=?new?HttpHeaders();
????????if?(ex?instanceof?ResultException)?{
????????????return?this.handleResultException((ResultException)?ex,?headers,?request);
????????}
????????//?TODO:?2019/10/05?galaxy?這里可以自定義其他的異常攔截
????????return?this.handleException(ex,?headers,?request);
????}
????/**?對(duì)ResultException類返回返回結(jié)果的處理?*/
????protected?ResponseEntity>?handleResultException(ResultException?ex,?HttpHeaders?headers,?WebRequest?request)?{
????????Result>?body?=?Result.failure(ex.getResultStatus());
????????HttpStatus?status?=?ex.getResultStatus().getHttpStatus();
????????return?this.handleExceptionInternal(ex,?body,?headers,?status,?request);
????}
????/**?異常類的統(tǒng)一處理?*/
????protected?ResponseEntity>?handleException(Exception?ex,?HttpHeaders?headers,?WebRequest?request)?{
????????Result>?body?=?Result.failure();
????????HttpStatus?status?=?HttpStatus.INTERNAL_SERVER_ERROR;
????????return?this.handleExceptionInternal(ex,?body,?headers,?status,?request);
????}
????/**
?????*?org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler#handleExceptionInternal(java.lang.Exception,?java.lang.Object,?org.springframework.http.HttpHeaders,?org.springframework.http.HttpStatus,?org.springframework.web.context.request.WebRequest)
?????*?
?????*?A?single?place?to?customize?the?response?body?of?all?exception?types.
?????*?
The?default?implementation?sets?the?{@link?WebUtils#ERROR_EXCEPTION_ATTRIBUTE}
?????*?request?attribute?and?creates?a?{@link?ResponseEntity}?from?the?given
?????*?body,?headers,?and?status.
?????*/
????protected?ResponseEntity>?handleExceptionInternal(
????????????Exception?ex,?Result>?body,?HttpHeaders?headers,?HttpStatus?status,?WebRequest?request)?{
????????if?(HttpStatus.INTERNAL_SERVER_ERROR.equals(status))?{
????????????request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE,?ex,?WebRequest.SCOPE_REQUEST);
????????}
????????return?new?ResponseEntity<>(body,?headers,?status);
????}
}
參考博客列表:
https://www.toutiao.com/i6694404645827117572/ https://blog.csdn.net/qq_36722039/article/details/80825117 http://www.imooc.com/article/260354 https://my.oschina.net/wangkang80/blog/1519189
來(lái)源:blog.csdn.net/qq_34347620/article/details/102239179
作者:小魏小魏我們?nèi)ツ抢镅?/span>
版權(quán)申明:內(nèi)容來(lái)源網(wǎng)絡(luò),版權(quán)歸原創(chuàng)者所有。除非無(wú)法確認(rèn),我們都會(huì)標(biāo)明作者及出處,如有侵權(quán)煩請(qǐng)告知,我們會(huì)立即刪除并表示歉意。謝謝!

