Spring统一数据返回格式处理String类型出错解析

Spring 统一数据返回格式是使用 Spring 进行开发时很常用的一个功能,但是当其处理返回类型原先为 String 类型的时候就会出错报错,需要我们额外对 String 类型进行处理。

例如:现在我开发一个项目,项目中我想要统一返回下述的 Result 类型:

@Data
public class Result {
    private ResultCodeEnum code;
    private String errMsg;
    private Object data;

    public static Result success(Object data) {
        Result result = new Result();
        result.setCode(ResultCodeEnum.SUCCESS);
        result.setErrMsg("");
        result.setData(data);

        return result;
    }
}

此时我就使用到 Spring 的统一数据返回格式功能:

@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) {
        if(body instanceof Result) {
            return body;
        }
        return Result.success(body);
    }
}

但是当我处理原本的返回类型为 String 的时候就出现以下报错:

@RestController
@RequestMapping("/test")
public class TestController {

    @RequestMapping("/m1")
    public String m1() {
        return "hello";
    }
}

其中涉及到的原因就得通过查看 Spring 的源码来进行分析:

SpringMVC 在进行初始化的时候,默认会注册⼀些⾃带的 HttpMessageConverter,MessageConverter 意思是转换器。并且会把它们存进一个名为 messageConverters 的 List 中,因此这些 HttpMessageConverter 存储起来是有先后顺序的,排列之后分别为:
1)ByteArrayHttpMessageConverter
2)StringHttpMessageConverter
3)SourceHttpMessageConverter
4)AllEncompassingFormHttpMessageConverter

这些从 RequestMappingHandlerAdapter 类中的构造方法即可知道:
( 该类正是对应着 SpringMVC 经常使用的路由映射 @RequestMapping 注解 )

其中 AllEncompassingFormHttpMessageConverter 会根据项⽬依赖情况添加对应的 HttpMessageConverter :

在依赖中引⼊jackson相关的 jar 包信息后,容器会把 MappingJackson2HttpMessageConverter ⾃动注册到 messageConverters 这个 List 的末尾。

Spring 会根据我们方法的返回值类型,从 messageConverters 中按顺序找到合适的 HttpMessageConverter 来进行转换。

因此报错原因就很容易找到了:由于 messageConverters 中,String 类的转换器比 JSON 数据格式对应的转换器顺序较前,因此当返回的数据是 String 时, StringHttpMessageConverter 会先被遍历到,这时会使用 StringHttpMessageConverter 。但是实际我们是需要封装成为一个 Result 类来进行返回,而 Spring 对于返回值为对象类型的都会处理返回成 JSON 字符串,因而应该对应的是 JSON 格式数据的转换器 MappingJackson2HttpMessageConverter 。由于转换器使用上出现了错误,势必就导致了使用时出现报错:

我们需要在 AbstractMessageConverterMethodProcessor 这个类中找到 writeWithMessageConverters 这个方法。我们使用统一返回数据格式处理这个功能时,所实现的 ResponseBodyAdvice 接口,具体实现逻辑就是在这个方法内:

找到这个方法中如下图所示的代码处:

上图中标识处就是使用 getAdvice 获取我们最开始代码中定义的 ResponseAdvice 类,并调用我们在该类中实现的 beforeBodyWrite 方法。调用之后一直来到下图中的蓝色标记处就出现了报错:

图中红色标记处,执行完 beforeBodyWrite 方法之后,body 就变成了 Result 类型( 因为我们在beforeBodyWrite 中定义好返回值就是 Result 类型 )。

因而到了蓝色标记处,调用父类( HttpMessageConverter )的 write 方法时,传入的 body 类型就是 Result 类型。

进入 write 方法,并选择 AbstractHttpMessageConverter 所实现的那个:

在这个方法中,使用的是泛型来接收我们传入的 body 参数,因此形参 t 也是 Result 类型,然后再调用 addDefaultHeaders 方法时传入 t 。

进入 addDefaultHeaders 方法,由于 AbstractHttpMessageConverter 的子类 StringHttpMessageConverter 重写了该方法,因此当调用该方法的时候,调用的是子类重写之后的方法( 因为是 StringHttpMessageConverter 引用来进行调用 ):

下述遍历 messageConverters 这个 List ,根据前面所说先遍历到了 StringHttpMessageConverter ,因此此时第二个红色标记处的 converter 指向的是 StringHttpMessageConverter 引用,因此是使用 StringHttpMessageConverter 引用来调用 write 方法,进而 write 内也是使用 StringHttpMessageConverter 引用来调用 addDefaultHeaders 方法。

从上图我们就知道了报错原因:StringHttpMessageConverter 类实现的 addDefaultHeaders 方法的第二个参数是使用 String 类型来进行接收,但是调用处传入的第二个参数 t,根据我们的一步步推断,是一个 Result 类型,类型不匹配,因此出现报错。

因此我们只需对返回值为 String 类型的进行特殊处理即可,使其正常使用 JSON 数据格式对应的转换器而不去使用 String 类型的转换器,类型就不会匹配错误,就不会报错:

@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) {
        if(body instanceof Result) {
            return body;
        }
        
        if(body instanceof String) {
            return objectMapper.writeValueAsString(Result.success(body));
        }
        return Result.success(body);
    }
}

上述代码中,判断了如果 body 是指向了 String 类型引用,就使用 Spring 内置的 ObjectMapping 对象将其转化为 JSON 字符串再返回。

从 AllEncompassingFormHttpMessageConverter 的构造方法中可以看到,如果返回值是一个 JSON 字符串,则会使用 JSON 字符串对应的转换器,问题解决。

解析重点概括如下:

  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值