简介
本文用实例介绍SpringBoot如何进行全局响应处理。
为什么进行全局响应处理?
在实际开发中,后端给前端的响应值必须要有一层封装,封装里边有状态码、错误信息、接口数据等。前端会先判断状态码,如果是成功,则获取数据;如果是失败,直接将错误信息提示给用户。
- 方便前后端交互
- 有了这个公共的响应值,前后端交互会变得统一,这样会很便捷高效,会减少很多问题。
- 方便feign调用
- 这个公共响应值也会作为feign的公共响应,便于调用方统一解析。
而这个封装响应值的操作,最优雅的做法是全局处理,因为这个公共响应体是个业务无关的东西。如果不全局处理,会有如下问题:
- 代码繁琐
- Controller都要这么写:ResultWrapper.success(data)。
- 会有人会直接返回数据,不封装
- 会有人直接将数据在不封装的情况下给前端,或提供给别人!导致项目不统一,难以维护。
- 如果有报错,无法将错误提示给调用方。因为没有封装的话,无法放到错误信息里!
上边这些问题会导致:项目问题很多、bug很多、无法维护。
最终效果
Controller
package com.knife.example.business.product.controller;
import com.knife.example.business.product.vo.ProductVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Api(tags = "商品")
@RestController
@RequestMapping("product")
public class ProductController {
@ApiOperation("查询详情")
@GetMapping("detail")
public ProductVO detail(Long id) {
//省略查数据库等逻辑
ProductVO productVO = new ProductVO();
productVO.setId(1L);
productVO.setName("鼠标");
productVO.setStockQuantity(1200);
return productVO;
}
}
测试
这里我用的knife4j,用postman也是一样的。

绿色箭头所指是响应值,可以发现,自动包装到了公共封装类里。
代码
方案简述
全局响应处理(处理返回值):@ControllerAdvice+ 实现ResponseBodyAdvice接口。
@ControllerAdvice所在类的beforBodyWrite方法将会在Controller方法执行之后执行。
下载源码
此隐藏内容仅限VIP查看升级VIP
代码结构

业务类
Controller
package com.knife.example.business.product.controller;
import com.knife.example.business.product.vo.ProductVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Api(tags = "商品")
@RestController
@RequestMapping("product")
public class ProductController {
@ApiOperation("查询详情")
@GetMapping("detail")
public ProductVO detail(Long id) {
//省略查数据库等逻辑
ProductVO productVO = new ProductVO();
productVO.setId(1L);
productVO.setName("鼠标");
productVO.setStockQuantity(1200);
return productVO;
}
}
VO
package com.knife.example.business.product.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("商品响应类")
public class ProductVO {
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("名字")
private String name;
@ApiModelProperty("库存数量")
private Integer stockQuantity;
}
全局响应处理类(核心)
package com.knife.example.common.advice;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.knife.example.common.constant.WrapperIgnoreUrl;
import com.knife.example.common.entity.ResultWrapper;
import lombok.extern.slf4j.Slf4j;
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.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
@Slf4j
@ControllerAdvice
public class GlobalResponseBodyAdvice implements ResponseBodyAdvice<Object> {
/**
* 返回值的含义:是否要处理
*/
@Override
public boolean supports(MethodParameter methodParameter,
Class<? extends HttpMessageConverter<?>> converterType) {
// 若接口返回的类型本身就是ResultWrapper,则无需操作,返回false
// return !methodParameter.getParameterType().equals(ResultWrapper.class);
// 本处全部都放过,在后边进行处理
return true;
}
@Override
@ResponseBody
public Object beforeBodyWrite(Object body, MethodParameter methodParameter,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (WrapperIgnoreUrl.isInWrapperIgnoreUrl(request.getURI().getPath())) {
// 如果不需要处理,直接跳过
return body;
}
Executable executable = methodParameter.getExecutable();
if (!(executable instanceof Method)) {
return body;
}
Method method = (Method) executable;
Class<?> returnType = method.getReturnType();
if (returnType == String.class) {
// 若返回值为String类型,需要包装为String类型返回。否则会报错
try {
ObjectMapper objectMapper = new ObjectMapper();
ResultWrapper<Object> resultWrapper = ResultWrapper.success().data(body);
return objectMapper.writeValueAsString(resultWrapper);
} catch (JsonProcessingException e) {
throw new RuntimeException("序列化String错误");
}
} else if (returnType == ResultWrapper.class) {
// 如果已经封装过了,不再封装
return body;
}
return ResultWrapper.success().data(body);
}
}
其他类
公共响应类
package com.knife.example.common.entity;
import com.knife.example.common.constant.ResultCode;
import lombok.Data;
@Data
public class ResultWrapper<T> {
private Boolean success = true;
private Integer code;
private T data;
private String message;
private ResultWrapper() {
}
public static <T> ResultWrapper<T> success() {
return success(null);
}
public static <T> ResultWrapper<T> success(T data) {
return assemble(ResultCode.SUCCESS.getCode(), true, data);
}
public static <T> ResultWrapper<T> error() {
return error(null);
}
public static <T> ResultWrapper<T> error(T data) {
return assemble(ResultCode.SYSTEM_FAILURE.getCode(), false, data);
}
public ResultWrapper<T> data(T data) {
this.setData(data);
return this;
}
public ResultWrapper<T> message(String message) {
this.setMessage(message);
return this;
}
public ResultWrapper<T> code(int code) {
this.setCode(code);
return this;
}
public static <T> ResultWrapper<T> assemble(int code, boolean success, T data) {
ResultWrapper<T> resultWrapper = new ResultWrapper<>();
resultWrapper.setCode(code);
resultWrapper.setSuccess(success);
resultWrapper.setData(data);
return resultWrapper;
}
}
返回结果码
package com.knife.example.common.constant;
public enum ResultCode {
SUCCESS(1000, "访问成功"),
SYSTEM_FAILURE(1001, "系统异常"),
;
private final int code;
private final String description;
ResultCode(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
}
不自动封装的URL
package com.knife.example.common.constant;
import org.springframework.util.AntPathMatcher;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 全局响应中不包装的url列表
*/
public interface WrapperIgnoreUrl {
List<String> KNIFE4J = Arrays.asList(
"/doc.html",
"/swagger-resources",
"/swagger-resources/configuration",
"/v3/api-docs",
"/v2/api-docs",
"/webjars/**");
List<String> ALL = new ArrayList<>(KNIFE4J);
static boolean isInWrapperIgnoreUrl(String uri) {
AntPathMatcher pathMatcher = new AntPathMatcher();
for (String s : WrapperIgnoreUrl.ALL) {
if (pathMatcher.match(s, uri)) {
return true;
}
}
return false;
}
}

请先 !