Spring Boot 统一数据返回格式 分析 和 处理

目录

实现统一数据格式

 测试 

 原因分析

 解决方案


  • 🎥 个人主页:Dikz12
  • 📕格言:吾愚多不敏,而愿加学
  • 欢迎大家👍点赞✍评论⭐收藏

    实现统一数据格式

    统⼀的数据返回格式使⽤ @ControllerAdvice 和 ResponseBodyAdvice 的⽅式实现; @ControllerAdvice : 表⽰控制器通知类. 比如:添加类 ResponseAdvice , 实现 ResponseBodyAdvice 接⼝, 并在类上添加 @ControllerAdvice 注解.
    import com.example.demo.model.Result;
    import org.springframework.core.MethodParameter;
    import org.springframework.http.MediaType;
    import org.springframework.http.server.ServerHttpRequest;
    import org.springframework.http.server.ServerHttpResponse;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
    @ControllerAdvice
    public class ResponseAdvice implements ResponseBodyAdvice {
        @Override
        public boolean supports(MethodParameter returnType, Class converterType) {
            return true;
        }
        @Override
        public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                      MediaType selectedContentType, Class selectedConverterType,
                                      ServerHttpRequest request, ServerHttpResponse response) {
            return Result.success(body); //返回 Result 类型的数据
        }
    }

     supports⽅法: 判断是否要执⾏beforeBodyWrite⽅法. true为执⾏, false不执⾏. 通过该⽅法可以 选择哪些类或哪些⽅法的response要进⾏处理, 其他的不进⾏处理.

    beforeBodyWrite⽅法:对response⽅法进⾏具体操作处理.

     测试 

     写一些不同的返回结果,看看哪些会出现问题!

    import com.example.demo.model.BookInfo;
    import com.example.demo.model.Result;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.RestController;
    @RestController
    @RequestMapping("/test")
    public class TestController {
        @RequestMapping("/t1")
        public Integer t1() {
            return 12334;
        }
        @RequestMapping("/t2")
        public String t2() {
            return "hello";
        }
        @RequestMapping("/t3")
        public Boolean t3() {
            return true;
        }
        @RequestMapping("/t4")
        public BookInfo t4() {
            return new BookInfo();
        }
        @RequestMapping("/t5")
        public Result t5() {
            return Result.success("success");
        }
    }
    

    1.返回结果为Integer,可以正确响应.

     

    2.返回结果为String,结果显示内部错误.

     

     控制台查看日志:

    3.返回结果为Boolean,可以正常响应.

     

     4.返回结果为BookInfo对象,可以正常响应.

    5.返回结果为Result,发现又进行了一次封装.

      

     问题:1.返回结果为String,不能正常进行处理了;2.返回结果为Result,又多进行了一次封装.

     原因分析

    这时候就会非常纳闷了,为什么就处理String的时候会出错呢?就不得不去看源码分析了.

    SpringMVC (也就是在初始化时) 默认会注册⼀些⾃带的 HttpMessageConverter (转换器) (从先后顺序排列分别为 ByteArrayHttpMessageConverter , StringHttpMessageConverter , SourceHttpMessageConverter , SourceHttpMessageConverter , AllEncompassingFormHttpMessageConverter )


     这些转换器是有先后顺序的,是用ArrayList存储的.会根据我们的返回结果挨个判断使用哪个转换器!!!

    此时,就会发现问题就出现在 StringHttpMessageConverter.

    处理的内容主要是在AbstractMessageConverterMethodProcessor 中 会有一个writeWithMessageConverters()方法.

    通过getAdvice()拿到了beforBodyWrite 就会对body进行处理,处理完之后并没有结束(body变成了Result类型),此时,也并没有出错。会继续执行((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage).

     由于 StringHttpMessageConverter 重写了addDefaultHeaders⽅法, 所以会执⾏⼦类的⽅法

    然⽽⼦类 StringHttpMessageConverter 的addDefaultHeaders⽅法定义接收参数为String, 此

    时t为Result类型, 所以出现类型不匹配" Result cannot be cast to java.lang.String "的异常.

     解决方案

    1.当返回结果为Result时,就直接返回body.

    2.当返回结果为String时,采用SpringBoot内置提供的Jackson来实现信息的序列化 

    @ControllerAdvice
    public class ResponseAdvice implements ResponseBodyAdvice {
        @Autowired
        private ObjectMapper objectMapper;
        @Override
        public boolean supports(MethodParameter returnType, Class converterType) {
            return true;
        }
        @SneakyThrows
        @Override
        public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                      MediaType selectedContentType, Class selectedConverterType,
                                      ServerHttpRequest request, ServerHttpResponse response) {
            //body 是返回的结果
            //当返回结果为Result类型时,就直接返回body
            if (body instanceof Result) {
                return body;
            }
            //返回结果为String类型, 使⽤SpringBoot内置提供的Jackson来实现信息的序列化
            if (body instanceof String) {
                return objectMapper.writeValueAsString(Result.success(body));
            }
            return Result.success(body); //返回 Result 类型的数据
        }
    }