文章目录
- [1. 问题铺垫](#1. 问题铺垫)
- [2. 解决方法](#2. 解决方法)
- [3. 问题分析](#3. 问题分析)
- [4 解决方法解释](#4 解决方法解释)
1. 问题铺垫
首先设置了以下代码统一处理返回类型
java
@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是:
有一个接口是这样的
此时访问:
看到日志:
提到Result不能被映射到String
2. 解决方法
java
@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
java
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
java
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
可以处理字符串的返回
核心问题就在这里:
java
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 = false
for (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 解决方法解释
java
@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,自然就不会发生类型匹配异常