开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情
前后端分离项目中,前端通常会使用统一封装的请求组件来做请求,同时在这个组件内进行统一的请求前、响应后处理,简化前端请求逻辑.
一般情况下,对于后端所有请求都要求有统一的返回格式,方便前端处理.
统一返回结果
借助springmvc的一个注解和一个增强类
- @RestControllerAdvice
- ResponseBodyAdvice
@RestControllerAdvice
看下结构:
本质上其实是@ControllerAdvice与@ResponseBody的结合体,顾名思义就是针对与返回json数据的.
ControllerAdvice其实就是springmvc提供的针对controller的增强类,使用此注解的类将作用于所有的Controller上,即所有的有RequestMapping注解的方法.
ResponseBodyAdvice
看下结构
提供了两个方法:
- supports
-
- 从名字就可以看出来,这个方法就是用来判断过来的请求是否要走这个增强. 简单的说就是返回true执行beforeBodyWrite方法,返回false不执行
- beforeBodyWrite
-
- 在这个方法内部进行返回内容的封装等操作.
简单原理概述:
- 启动后,扫描带有@ControllerAdvice注解的bean,放入RequestResponseBodyAdviceChain中
- 在请求到达后和请求结束后转换json数据时,选择合适的advice进行增强
我们主要使用beforeBodyWrite方法,即请求结束后增强响应
使用
package com.zy.common.advice;
import com.zy.common.model.response.ResponseResult;
import org.springframework.beans.factory.annotation.Value;
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.stereotype.Component;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* @Author: Zy
* @Date: 2023/1/3 9:25
*/
@RestControllerAdvice
@Component
public class ResponseResultAdvice implements ResponseBodyAdvice<Object> {
@Value("${advice.result:true}")
Boolean resultAdviceEnabled;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return ResponseResult.success(body);
}
}
- 使用@RestControllerAdvice增强
- 实现ResponseBodyAdvice接口,标识此类是一个advice增强类,同时@ResponseBodyAdvice中有@Comment注解,可以注册进入Spring
- supports方法中直接返回true,表示所有请求均被增强
- beforeBodyWrite方法中对响应内容进行包装,包装的方法自定义,返回统一格式结果即可.
ResponseResult
上文中对响应内容进行包装的方法:
package com.zy.common.model.response;
import lombok.Data;
/**
* @Author: Zy
* @Date: 2022/12/9 11:03
* 统一返回结果包装类
*/
@Data
public class ResponseResult {
private String code;
private String msg;
private Object data;
public ResponseResult(ResponseCode code, String msg) {
this.code = code.getCode();
this.msg = msg;
}
public ResponseResult(ResponseCode code, String msg, Object data) {
this.code = code.getCode();
this.msg = msg;
this.data = data;
}
public static ResponseResult success() {
return new ResponseResult(ResponseCode.SUCCESS, "success");
}
public static ResponseResult success(String msg) {
return new ResponseResult(ResponseCode.SUCCESS, msg);
}
public static ResponseResult success(String msg, Object data) {
return new ResponseResult(ResponseCode.SUCCESS, msg, data);
}
public static ResponseResult success(Object data) {
return new ResponseResult(ResponseCode.SUCCESS, "success", data);
}
public static ResponseResult error() {
return new ResponseResult(ResponseCode.ERROR, "error");
}
public static ResponseResult error(String msg) {
return new ResponseResult(ResponseCode.ERROR, msg);
}
public static ResponseResult error(Object data) {
return new ResponseResult(ResponseCode.ERROR, "error", data);
}
public static ResponseResult error(String msg, Object data) {
return new ResponseResult(ResponseCode.ERROR, msg, data);
}
}
可以自定义格式,再对外暴露几个简单方法即可.
统一异常处理
统一异常初始同样基于@RestControllerAdvice,区别是不用实现ResponseBodyAdvice接口,而是改用@ExceptionHandler注解.
@ExceptionHandler
与@RestControllerAdvice配套使用,只有一个属性value,用于表示用此注解修饰的方法用来捕获什么类型的异常
使用
package com.zy.common.advice;
import com.zy.common.model.response.ResponseResult;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.lang.annotation.Annotation;
/**
* @Author: Zy
* @Date: 2023/1/3 11:18
*/
@RestControllerAdvice
public class ExceptionHandlerAdvice {
@ExceptionHandler(Exception.class)
public ResponseResult handleException(Exception e){
e.printStackTrace();
return ResponseResult.error(e.getMessage());
}
}
简单的直接捕获Exception,可以分为多种异常,或者捕获自定义异常,进行特殊操作.
冲突
统一异常处理与统一返回结果存在冲突,因为所有请求都会经过统一返回结果处理,如果出现了异常,请求就会被增强两次,导致响应体被包装两次.
解决办法:
在统一返回结果的supports中增加过滤,如果是已经被包装过的结果就不再进行二次包装:
package com.zy.common.advice;
import com.zy.common.model.response.ResponseResult;
import org.springframework.beans.factory.annotation.Value;
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.stereotype.Component;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
/**
* @Author: Zy
* @Date: 2023/1/3 9:25
*/
@RestControllerAdvice
@Component
public class ResponseResultAdvice implements ResponseBodyAdvice<Object> {
@Value("${advice.result:true}")
Boolean resultAdviceEnabled;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 如果增加类未启用 或者 返回的类型已经是结果类了 就不再进行封装
return resultAdviceEnabled && !(returnType.getParameterType() == ResponseResult.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
return ResponseResult.success(body);
}
}