文章目录
- 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是:
有一个接口是这样的
此时访问:
看到日志:
提到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对象装换为响应消息体中的数据类型
Spring MVC会默认注册一些自带的HttpMessageConverter
(从先后顺序排序分别为:
ByteArrayHttpMessageConverter
StringHttpMessageConverter
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
可以处理字符串的返回
核心问题就在这里:
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
中
但是! 关键来了:由于上面的converter实例是StringHttpMessageConverte
,而StringHttpMessageConverte
是AbstractHttpMessageConverter
的子类,重写了addDefaultHeader*
方法,因此此时调用的是StringHttpMessageConvert.addDefaultHeaders
!!!
如果这里不理解没关系,我们举个类似的例子:
执行结果:
回到正题,此时执行的是StringHttpMessageConvert.addDefaultHeaders
:
但是由于
在这里调用的时候,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,自然就不会发生类型匹配异常