简介
本文用实例介绍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; } }
请先
!