欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 家装 > Spring统一返回类型中关于String的问题

Spring统一返回类型中关于String的问题

2025/6/8 22:40:27 来源:https://blog.csdn.net/m0_60963435/article/details/140889316  浏览:    关键词:Spring统一返回类型中关于String的问题

文章目录

  • 1. 问题铺垫
  • 2. 解决方法
  • 3. 问题分析
  • 4 解决方法解释

1. 问题铺垫

首先设置了以下代码统一处理返回类型

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {return Result.success(body);}
}

其中Result是:
image.png
有一个接口是这样的
image.png
此时访问:
image.png
看到日志:
image.png
提到Result不能被映射到String

2. 解决方法

@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if(body instanceof String){try {if("SUCCESS".equals(body)){response.getHeaders().setContentType(MediaType.APPLICATION_JSON);return objectMapper.writeValueAsString(Result.success(body));}else {return objectMapper.writeValueAsString(Result.paramError());}} catch (JsonProcessingException e) {throw new RuntimeException(e);}}return Result.success(body);
}

就是要对String的返回类型进行特殊处理,转成JSON字符串

3. 问题分析

在Spring MVC中,HttpMessageConverter 接口定义了在HTTP请求的发送和接受的过程中,如何将请求消息体中的数据转化为java对象,以及如何将java对象装换为响应消息体中的数据类型
image.png
Spring MVC会默认注册一些自带的HttpMessageConverter(从先后顺序排序分别为:

  1. ByteArrayHttpMessageConverter
  2. StringHttpMessageConverter
  3. AllEncompassingFormHttpMessageConverter
private final List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
//...private void initMessageConverters() {if (!this.messageConverters.isEmpty()) {return;}this.messageConverters.add(new ByteArrayHttpMessageConverter());this.messageConverters.add(new StringHttpMessageConverter());this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}

Spring MVC 允许开发者通过配置来注册自定义的 HttpMessageConverter 实现,AllEncompassingFormHttpMessageConverter会根据项目依赖的情况,添加对应的HttpMessageConverter

public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConverter {private static final boolean jaxb2Present;private static final boolean jackson2Present;private static final boolean jackson2XmlPresent;private static final boolean jackson2SmilePresent;private static final boolean gsonPresent;private static final boolean jsonbPresent;private static final boolean kotlinSerializationCborPresent;private static final boolean kotlinSerializationJsonPresent;private static final boolean kotlinSerializationProtobufPresent;static {ClassLoader classLoader = AllEncompassingFormHttpMessageConverter.class.getClassLoader();jaxb2Present = ClassUtils.isPresent("jakarta.xml.bind.Binder", classLoader);jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader);kotlinSerializationCborPresent = ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", classLoader);kotlinSerializationJsonPresent = ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader);kotlinSerializationProtobufPresent = ClassUtils.isPresent("kotlinx.serialization.protobuf.ProtoBuf", classLoader);}public AllEncompassingFormHttpMessageConverter() {if (jaxb2Present && !jackson2XmlPresent) {addPartConverter(new Jaxb2RootElementHttpMessageConverter());}if (kotlinSerializationJsonPresent) {addPartConverter(new KotlinSerializationJsonHttpMessageConverter());}if (jackson2Present) {addPartConverter(new MappingJackson2HttpMessageConverter());}else if (gsonPresent) {addPartConverter(new GsonHttpMessageConverter());}else if (jsonbPresent) {addPartConverter(new JsonbHttpMessageConverter());}if (jackson2XmlPresent) {addPartConverter(new MappingJackson2XmlHttpMessageConverter());}if (jackson2SmilePresent) {addPartConverter(new MappingJackson2SmileHttpMessageConverter());}if (kotlinSerializationCborPresent) {addPartConverter(new KotlinSerializationCborHttpMessageConverter());}if (kotlinSerializationProtobufPresent) {addPartConverter(new KotlinSerializationProtobufHttpMessageConverter());}}}

当我们在依赖中引入jackson包(Spring自动引入)后,容器会将MappingJackson2HttpMessageConverter 自动添加到messageConverters末尾,用于处理json数据
处理数据数据时,Spring会根据返回的数据类型,从messageConverters链中选择合适的HttpMessageConverter
当Controller返回一个非字符串类型时,使用的是MappingJackson2XmlHttpMessageConverter写入对象
当返回的数据是字符串时,StringHttpMessageConverte会先被遍历到,并且认为StringHttpMessageConverte可以处理字符串的返回
image.png

核心问题就在这里:

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {//...代码省略if (selectedMediaType != null) {selectedMediaType = selectedMediaType.removeQualityValue();//遍历//GenericHttpMessageConverter用于处理复杂的数据类型,//此时converter是StringHttpMessageConverte,用于处理简单的字符串数据//因此converter instanceof GenericHttpMessageConverter = falsefor (HttpMessageConverter<?> converter : this.messageConverters) {GenericHttpMessageConverter genericConverter =(converter instanceof GenericHttpMessageConverter ghmc ? ghmc : null);//在这里判断当前converter是否可以处理当前数据,此时如果数据是String,//StringHttpMessageConverte可以直接处理if (genericConverter != null ?((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :converter.canWrite(valueType, selectedMediaType)) {//调用getAdvice().beforeBodyWrite, 执⾏之后, //body转换成了我们自定义的Result类型的body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,(Class<? extends HttpMessageConverter<?>>) converter.getClass(),inputMessage, outputMessage);if (body != null) {Object theBody = body;LogFormatUtils.traceDebug(logger, traceOn ->"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");addContentDispositionHeader(inputMessage, outputMessage);if (genericConverter != null) {genericConverter.write(body, targetType, selectedMediaType, outputMessage);}else {//走的是这里((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);}}else {if (logger.isDebugEnabled()) {logger.debug("Nothing to write: null body");}}return;}}}//...代码省略
}

当执行到((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage) ,
注意:此时的converter实例是StringHttpMessageConverte
接着会调用write方法,是在:AbstractHttpMessageConverter
image.png
但是! 关键来了:由于上面的converter实例是StringHttpMessageConverte,而StringHttpMessageConverteAbstractHttpMessageConverter的子类,重写了addDefaultHeader*方法,因此此时调用的是
StringHttpMessageConvert.addDefaultHeaders!!!

如果这里不理解没关系,我们举个类似的例子:
image.png
执行结果:image.png

回到正题,此时执行的是StringHttpMessageConvert.addDefaultHeaders:

image.png


但是由于

image.png

在这里调用的时候,t是我们最开始封装的Result类型,Result -> String,就会抛出Result cannot be cast to class java.lang.String异常

4 解决方法解释

@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if(body instanceof String){try {if("SUCCESS".equals(body)){response.getHeaders().setContentType(MediaType.APPLICATION_JSON);return objectMapper.writeValueAsString(Result.success(body));}else {return objectMapper.writeValueAsString(Result.paramError());}} catch (JsonProcessingException e) {throw new RuntimeException(e);}}return Result.success(body);
}

此时我们对String的返回类型进行了特判,转化成JSON字符串,此时就是以String类型去处理,而不会转成Result,自然就不会发生类型匹配异常

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词