最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

【全球热闻】SpringBoot中统一API返回格式的两种方式

来源:博客园


(资料图片仅供参考)

微服务中,由于各业务团队之间的对接,各个团队之间需要统一返回格式,这样解析时不容易出现错误。因此,有必要统一返回格式。下面我说下项目中常见的两种统一和变更返回值格式的方式

ResponseBodyAdvice切面方式

这种方式简单易实现,仅仅只需要实现ResponseBodyAdvice方法,然后指定要拦截的包路径即可

@ControllerAdvice("com.example.ut")public class RestControllerAdvice implements ResponseBodyAdvice {    private static final String VOID = "void";    //判断是否要执行beforeBodyWrite方法,true为执行,false不执行    @Override    public boolean supports(MethodParameter methodParameter, Class> aClass) {        return true;    }    @Override    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,                                  Class> selectedConverterType, ServerHttpRequest request,                                  ServerHttpResponse response) {        if (VOID.equals(getReturnName(returnType))) {            return null;        }        //基础类型做特殊处理,如实际操作过程没有此场景可无须使用        if (isBasicType(returnType)) {            return body;        }        if (body == null) {            return  ApiResponse.of(null,StatusCode.OK);        }        if (!(body instanceof ApiResponse)) {            return  ApiResponse.of(body, StatusCode.OK);        }        else {            ApiResponse commonResult = (ApiResponse) body;            return commonResult;        }    }    private String getReturnName(MethodParameter returnType) {        if (returnType == null || returnType.getMethod() == null) {            return StringUtils.EMPTY;        }        return returnType.getMethod().getReturnType().getName();    }    private boolean isBasicType(MethodParameter returnType) {        if (returnType == null || returnType.getMethod() == null) {            return true;        }        String name = returnType.getMethod().getReturnType().getSimpleName();        switch (name) {            case "String":            case "byte[]":            case "ResponseEntity":                return true;            default:                return false;        }    }}

测试时使用通用的返回通用类作为测试依据当我们再返回值没有使用ApiResponse作为包装对象时,此切面仍然为我们实现了包装

@RestControllerpublic class ResponseController {    @PostMapping("test")    public Circle testReturn(){       Circle circle =  new Circle();       circle.setRadius(5.0d);        return circle;    }}

返回值为

{    "code": 0,    "message": "OK",    "body": {        "radius": 5.0,        "area": 78.53981633974483    }}

究其原因则是因为在源码中,初始化时就对切面进行了处理,从而可以执行相应的操作,具体可以参考RequestMappingHandlerAdapter#initControllerAdviceCache

使用更为底层的HandlerMethodReturnValueHandler来自定义返回值类型

在操作的过程中也是同样的逻辑

public class ApiResponseHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {    private MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();    //是否支持handleReturnValue    @Override    public boolean supportsReturnType(MethodParameter returnType) {        return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||                returnType.hasMethodAnnotation(ResponseBody.class))                && !ApiResponse.class.equals(returnType.getParameterType());    }    @Override    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,                                  NativeWebRequest webRequest) throws Exception {        // TODO 可通过客户端的传递的请求头来切换不同的响应体的内容        mavContainer.setRequestHandled(true);        // returnValue =  POJO        ApiResponse apiResponse = ApiResponse.ok(returnValue);        HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse();        response.addHeader("v", "3");        ServletServerHttpResponse httpOutMessage = createOutputMessage(webRequest);        converter.write(apiResponse, MediaType.APPLICATION_JSON, httpOutMessage);    }    protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);        Assert.state(response != null, "No HttpServletResponse");        return new ServletServerHttpResponse(response);    }}

但是由于spring的默认处理类是RequestResponseBodyMethodProcessor,它是根据判断是否有@ResponseBody注解来处理的且他是在自定义HandlerMethodReturnValueHandler之前执行的,所以我们需要把我们的自定义且他是在自定义HandlerMethodReturnValueHandler放到最前面执行才可以

@Configurationpublic class WebMvcConfiguration {    @Autowired    public void resetRequestMappingHandlerAdapter(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {        List oldReturnValueHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();        List newReturnValueHandlers = new ArrayList<>(oldReturnValueHandlers);        newReturnValueHandlers.add(0, new ApiResponseHandlerMethodReturnValueHandler());        requestMappingHandlerAdapter.setReturnValueHandlers(newReturnValueHandlers);    }}

源码执行顺序如下

private List getDefaultReturnValueHandlers() {        List handlers = new ArrayList(20);        handlers.add(new ModelAndViewMethodReturnValueHandler());        handlers.add(new ModelMethodProcessor());        handlers.add(new ViewMethodReturnValueHandler());        handlers.add(new ResponseBodyEmitterReturnValueHandler(this.getMessageConverters(), this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));        handlers.add(new StreamingResponseBodyReturnValueHandler());        handlers.add(new HttpEntityMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));        handlers.add(new HttpHeadersReturnValueHandler());        handlers.add(new CallableMethodReturnValueHandler());        handlers.add(new DeferredResultMethodReturnValueHandler());        handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));        handlers.add(new ServletModelAttributeMethodProcessor(false));        //先执行了RequestResponseBodyMethodProcessor        handlers.add(new RequestResponseBodyMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));        handlers.add(new ViewNameMethodReturnValueHandler());        handlers.add(new MapMethodProcessor());        //后执行自定义HandlerMethodReturnValueHandler        if (this.getCustomReturnValueHandlers() != null) {            handlers.addAll(this.getCustomReturnValueHandlers());        }        if (!CollectionUtils.isEmpty(this.getModelAndViewResolvers())) {            handlers.add(new ModelAndViewResolverMethodReturnValueHandler(this.getModelAndViewResolvers()));        } else {            handlers.add(new ServletModelAttributeMethodProcessor(true));        }        return handlers;    }

这样即可达到与上述ResponseBodyAdvice切面方式一样的效果

参考文章Spring Boot 中如何统一 API 接口响应格式?

关键词: 也是同样 两种方式 我们需要