前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot中统一API返回格式的两种方式

SpringBoot中统一API返回格式的两种方式

原创
作者头像
zhaozhen
发布2023-02-18 23:49:27
7890
发布2023-02-18 23:49:27
举报
文章被收录于专栏:微瞰Java后端开发

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

ResponseBodyAdvice切面方式

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

代码语言:txt
复制
@ControllerAdvice("com.example.ut")
public class RestControllerAdvice implements ResponseBodyAdvice<Object> {

    private static final String VOID = "void";
    //判断是否要执行beforeBodyWrite方法,true为执行,false不执行
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> 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作为包装对象时,此切面仍然为我们实现了包装

代码语言:txt
复制
@RestController
public class ResponseController {
    @PostMapping("test")
    public Circle testReturn(){
       Circle circle =  new Circle();
       circle.setRadius(5.0d);
        return circle;
    }
}

返回值为

代码语言:txt
复制
{
    "code": 0,
    "message": "OK",
    "body": {
        "radius": 5.0,
        "area": 78.53981633974483
    }
}

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

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

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

代码语言:txt
复制
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放到最前面执行才可以

代码语言:txt
复制
@Configuration
public class WebMvcConfiguration {

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

}

源码执行顺序如下

代码语言:txt
复制
    private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
        List<HandlerMethodReturnValueHandler> 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 接口响应格式?

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ResponseBodyAdvice切面方式
  • 使用更为底层的HandlerMethodReturnValueHandler来自定义返回值类型
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档