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

Posted 微瞰技术

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot中统一API返回格式的两种方式相关的知识,希望对你有一定的参考价值。

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

ResponseBodyAdvice切面方式

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

@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作为包装对象时,此切面仍然为我们实现了包装

@RestController
public 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放到最前面执行才可以

@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);
    


源码执行顺序如下

    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 接口响应格式?

以上是关于SpringBoot中统一API返回格式的两种方式的主要内容,如果未能解决你的问题,请参考以下文章

springboot之jackson的两种配置方式

SpringBoot——SpringBoot搭建非web应用的两种方式

springboot项目启动成功后执行一段代码的两种方式

SpringBoot热部署的两种方式

JavaWeb 返回json数据的两种方式

SpringBoot——SpringBoot中使用Servlet的两种方式