返回值处理与消息转换源码分析
一、概述
Controller 方法执行后,返回值需经过 HandlerMethodReturnValueHandler 处理,最终转换为 HTTP 响应。RequestMappingHandlerAdapter 负责整个流程的协调。
二、HandlerMethodReturnValueHandler 体系
2.1 核心接口
java
public interface HandlerMethodReturnValueHandler {
boolean supportsReturnType(MethodParameter returnType);
void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}2.2 默认处理器列表
| 处理器 | 处理的返回值类型 | 优先级 |
|---|---|---|
| ModelAndViewMethodReturnValueHandler | ModelAndView | 低 |
| ViewMethodReturnValueHandler | View | 低 |
| ViewNameMethodReturnValueHandler | CharSequence | 低 |
| MapMethodProcessor | Map | 低 |
| CallableMethodReturnValueHandler | Callable | 高 |
| DeferredResultMethodReturnValueHandler | DeferredResult / ListenableFuture / CompletionStage | 高 |
| AsyncTaskMethodReturnValueHandler | WebAsyncTask | 高 |
| RequestResponseBodyMethodProcessor | @ResponseBody | 高 |
| HttpHeadersReturnValueHandler | HttpHeaders | 中 |
| StreamingResponseBodyReturnValueHandler | StreamingResponseBody | 高 |
| HttpEntityMethodProcessor | ResponseEntity / HttpEntity | 高 |
处理时按注册顺序遍历,找到第一个 supportsReturnType 返回 true 的处理器执行。
2.3 处理器注册源码
java
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();
handlers.add(new ModelAndViewMethodReturnValueHandler());
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new ResponseBodyEmitterReturnValueHandler(this.getMessageConverters()));
handlers.add(new StreamingResponseBodyReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(this.getMessageConverters(),
this.contentNegotiationManager, this.requestResponseBodyAdvice));
handlers.add(new HttpHeadersReturnValueHandler());
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler());
handlers.add(new ModelAttributeMethodProcessor(true));
handlers.add(new RequestResponseBodyMethodProcessor(
this.getMessageConverters(), this.contentNegotiationManager,
this.requestResponseBodyAdvice));
handlers.add(new ViewNameMethodReturnValueHandler());
handlers.add(new MapMethodProcessor());
handlers.add(new ModelAttributeMethodProcessor(false));
return handlers;
}2.4 自定义处理器注册
java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
handlers.add(0, new CustomReturnValueHandler());
}
}2.5 HandlerMethodReturnValueHandlerComposite
采用组合模式统一调度:
java
public class HandlerMethodReturnValueHandlerComposite
implements HandlerMethodReturnValueHandler {
private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: "
+ returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
@Nullable
private HandlerMethodReturnValueHandler selectHandler(MethodParameter returnType) {
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (handler.supportsReturnType(returnType)) return handler;
}
return null;
}
}三、@ResponseBody 处理流程
3.1 RequestResponseBodyMethodProcessor
java
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return AnnotatedElementUtils.hasAnnotation(
returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class);
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
mavContainer.setRequestHandled(true); // 跳过视图解析
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
}3.2 关键步骤
- supportsReturnType:检查方法或类上是否存在
@ResponseBody/@RestController - setRequestHandled(true):标记请求已完成,不再进行视图解析
- 内容协商:通过
ContentNegotiationManager确定客户端期望的媒体类型 - 转换器匹配:遍历消息转换器列表,按类型和媒体类型匹配
- Advice 拦截:执行
ResponseBodyAdvice.beforeBodyWrite()前置处理 - 写入响应:调用选中转换器的
write()方法将对象写入 HTTP 输出流
3.3 内容协商配置
java
@Configuration
public class ContentNegotiationConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.parameterName("format")
.favorParameter(true)
.ignoreAcceptHeader(false)
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
}四、HttpMessageConverter 转换器链
4.1 核心接口
java
public interface HttpMessageConverter<T> {
boolean canRead(Class<?> clazz, MediaType mediaType);
boolean canWrite(Class<?> clazz, MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException;
}GenericHttpMessageConverter 扩展泛型支持(Spring 4.2+):
java
public interface GenericHttpMessageConverter<T> extends HttpMessageConverter<T> {
boolean canRead(Type type, Class<?> contextClass, MediaType mediaType);
T read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException;
boolean canWrite(Type type, Class<?> clazz, MediaType mediaType);
void write(T t, Type type, MediaType contentType, HttpOutputMessage outputMessage) throws IOException;
}4.2 默认转换器链
| 转换器 | 处理类型 | 条件 |
|---|---|---|
| ByteArrayHttpMessageConverter | byte[] | 始终注册 |
| StringHttpMessageConverter | String | 始终注册 |
| ResourceHttpMessageConverter | Resource | 始终注册 |
| ResourceRegionHttpMessageConverter | ResourceRegion | 始终注册 |
| MappingJackson2HttpMessageConverter | JSON | classpath 有 Jackson2 |
| MappingJackson2XmlHttpMessageConverter | XML | classpath 有 Jackson2Xml |
| Jaxb2RootElementHttpMessageConverter | XML | classpath 有 JAXB2 |
| AllEncompassingFormHttpMessageConverter | 表单数据 | 始终注册 |
4.3 AbstractHttpMessageConverter 模板
java
public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {
@Override
public final void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
HttpHeaders headers = outputMessage.getHeaders();
addDefaultHeaders(headers, t, contentType);
if (outputMessage instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streaming = (StreamingHttpOutputMessage) outputMessage;
streaming.setBody(os -> writeInternal(t, /* 包装 */));
} else {
writeInternal(t, outputMessage);
outputMessage.getBody().flush();
}
}
protected abstract void writeInternal(T t, HttpOutputMessage outputMessage) throws IOException;
}五、MappingJackson2HttpMessageConverter 源码分析
5.1 类层次
MappingJackson2HttpMessageConverter
extends AbstractJackson2HttpMessageConverter
extends AbstractGenericHttpMessageConverter<Object>
extends AbstractHttpMessageConverter<Object>
implements GenericHttpMessageConverter<Object>5.2 构造与初始化
java
public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
public MappingJackson2HttpMessageConverter() {
this(Jackson2ObjectMapperBuilder.json().build());
}
public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
}5.3 核心序列化逻辑
java
public abstract class AbstractJackson2HttpMessageConverter
extends AbstractGenericHttpMessageConverter<Object> {
protected ObjectMapper objectMapper;
@Override
protected void writeInternal(Object object, @Nullable Type type,
HttpOutputMessage outputMessage) throws IOException {
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
JsonGenerator generator = objectMapper.getFactory()
.createGenerator(outputMessage.getBody(), encoding);
try {
writePrefix(generator, object);
ObjectWriter objectWriter;
if (type instanceof Class) {
objectWriter = objectMapper.writerWithView((Class<?>) type); // @JsonView
} else {
objectWriter = objectMapper.writer();
}
if (this.prettyPrint != null && this.prettyPrint) {
objectWriter = objectWriter.with(SerializationFeature.INDENT_OUTPUT);
}
objectWriter.writeValue(generator, object);
writeSuffix(generator, object);
generator.flush();
} catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException(
"Could not write JSON: " + ex.getOriginalMessage(), ex);
}
}
@Override
protected Object readInternal(Class<Object> clazz, HttpInputMessage inputMessage)
throws IOException {
JavaType javaType = getJavaType(clazz, null);
try {
return objectMapper.readValue(inputMessage.getBody(), javaType);
} catch (JsonProcessingException ex) {
throw new HttpMessageNotReadableException(
"Could not read JSON: " + ex.getOriginalMessage(), ex);
}
}
}5.4 Spring Boot ObjectMapper 自动配置
properties
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=Asia/Shanghai
spring.jackson.serialization.write-dates-as-timestamps=false
spring.jackson.default-property-inclusion=non_null六、writeWithMessageConverters 深度分析
定义在 AbstractMessageConverterMethodProcessor 中,是消息转换的核心调度方法:
java
protected <T> void writeWithMessageConverters(@Nullable T value,
MethodParameter returnType, ServletServerHttpRequest inputMessage,
ServletServerHttpResponse outputMessage) throws IOException {
Object body = value;
Class<?> valueType = getReturnValueType(body, returnType);
Type targetType = getGenericType(returnType);
if (value instanceof CharSequence) body = value.toString();
// 步骤一:内容协商确定媒体类型
MediaType selectedMediaType = null;
MediaType contentType = outputMessage.getHeaders().getContentType();
if (contentType != null && contentType.isConcrete()) {
selectedMediaType = contentType;
} else {
HttpServletRequest request = inputMessage.getServletRequest();
List<MediaType> acceptable = getAcceptableMediaTypes(request);
List<MediaType> producible = getProducibleMediaTypes(request, valueType, targetType);
if (body != null) {
for (MediaType a : acceptable) for (MediaType p : producible) {
if (a.isCompatibleWith(p)) {
selectedMediaType = getMostSpecificMediaType(a, p);
break;
}
}
}
if (selectedMediaType == null) selectedMediaType = producible.get(0);
}
// 步骤二:遍历转换器匹配并写入
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter<?> gc = (converter instanceof GenericHttpMessageConverter)
? (GenericHttpMessageConverter<?>) converter : null;
boolean canWrite = (gc != null)
? gc.canWrite(targetType, valueType, selectedMediaType)
: converter.canWrite(valueType, selectedMediaType);
if (canWrite) {
body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
addResponseHeaders(outputMessage, returnType);
if (gc != null) gc.write(body, targetType, selectedMediaType, outputMessage);
else ((HttpMessageConverter<Object>) converter).write(body, selectedMediaType, outputMessage);
}
return;
}
}
}
if (body != null) throw new HttpMediaTypeNotAcceptableException(
getProducibleMediaTypes(request, valueType, targetType));
}
protected List<MediaType> getProducibleMediaTypes(
HttpServletRequest request, Class<?> valueClass, Type targetType) {
Set<MediaType> mediaTypes = new LinkedHashSet<>();
List<MediaType> attr = (List<MediaType>) request
.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (attr != null && !attr.isEmpty()) mediaTypes.addAll(attr);
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter
&& ((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)
|| converter.canWrite(valueClass, null)) {
mediaTypes.addAll(converter.getSupportedMediaTypes());
}
}
if (mediaTypes.isEmpty()) mediaTypes.add(MediaType.ALL);
return new ArrayList<>(mediaTypes);
}七、ResponseBodyAdvice 拦截机制
java
public interface ResponseBodyAdvice<T> {
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
@Nullable T beforeBodyWrite(@Nullable T body, MethodParameter returnType,
MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);
}7.1 全局响应包装
java
@ControllerAdvice
public class GlobalResponseBodyAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof ApiResponse) return body; // 避免二次包装
return ApiResponse.success(body);
}
}八、自定义序列化方案
8.1 日期格式统一
配置文件全局统一:
properties
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=Asia/Shanghai
spring.jackson.serialization.write-dates-as-timestamps=falseJackson2ObjectMapperBuilder 定制:
java
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilder builder() {
return new Jackson2ObjectMapperBuilder()
.dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
.timeZone(TimeZone.getTimeZone("Asia/Shanghai"))
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.modules(new JavaTimeModule());
}
}自定义序列化器:
java
public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
private static final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider p) throws IOException {
gen.writeString(value.format(fmt));
}
}
SimpleModule module = new SimpleModule();
module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
objectMapper.registerModule(module);@JsonFormat 注解(字段级):
java
public class User {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date createTime;
}8.2 字段过滤方案
@JsonIgnore / @JsonIgnoreProperties:
java
public class User { @JsonIgnore private String password; }
@JsonIgnoreProperties({"password"}) public class User {}@JsonView 动态视图:
java
public class Views { public static class Public {} public static class Internal extends Public {} }
public class User {
@JsonView(Views.Public.class) private String username;
@JsonView(Views.Internal.class) private String phone;
}
@RestController
public class UserController {
@GetMapping("/user") @JsonView(Views.Public.class) public User getUser() { return service.getUser(); }
}SimpleBeanPropertyFilter:
java
@JsonFilter("userFilter") public class User {}
@Configuration
public class JacksonFilterConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper m = new ObjectMapper();
m.setFilterProvider(new SimpleFilterProvider().addFilter("userFilter",
SimpleBeanPropertyFilter.serializeAllExcept("password")));
return m;
}
}自定义序列化器脱敏:
java
public class PhoneSerializer extends JsonSerializer<String> {
@Override public void serialize(String v, JsonGenerator gen, SerializerProvider p) throws IOException {
gen.writeString(v != null && v.length() > 7
? v.substring(0, 3) + "****" + v.substring(v.length() - 4) : v);
}
}
public class User { @JsonSerialize(using = PhoneSerializer.class) private String phone; }8.3 自定义 HttpMessageConverter
java
public class CustomJsonConverter extends MappingJackson2HttpMessageConverter {
public CustomJsonConverter() {
ObjectMapper m = new ObjectMapper();
m.setSerializationInclusion(JsonInclude.Include.NON_NULL);
m.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
m.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
m.registerModule(new JavaTimeModule());
m.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
setObjectMapper(m);
}
}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new CustomJsonConverter());
}
}8.4 Jackson MixIn 多版本序列化
java
public class User { String username; String phone; String password; Date createTime; }
public interface V1MixIn { @JsonIgnore String getPhone(); @JsonIgnore String getPassword(); }
public interface V2MixIn {
@JsonIgnore String getPassword();
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date getCreateTime();
}
@Configuration
public class VersionedConfig {
@Bean("mapperV1") public ObjectMapper mapperV1() {
ObjectMapper m = new ObjectMapper(); m.addMixIn(User.class, V1MixIn.class); return m;
}
@Bean("mapperV2") public ObjectMapper mapperV2() {
ObjectMapper m = new ObjectMapper(); m.addMixIn(User.class, V2MixIn.class);
m.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); return m;
}
}九、返回值处理器注册机制
9.1 RequestMappingHandlerAdapter 初始化
java
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements InitializingBean {
@Nullable private List<HandlerMethodReturnValueHandler> returnValueHandlers;
@Nullable private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
@Override
public void afterPropertiesSet() {
initControllerAdviceCache();
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
if (this.customReturnValueHandlers != null) handlers.addAll(0, this.customReturnValueHandlers);
this.returnValueHandlers = handlers;
}
}
}9.2 ServletInvocableHandlerMethod 调用链
java
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
public void invokeAndHandle(ServletWebRequest webRequest,
ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
if (returnValue == null && (isRequestNotModified(webRequest)
|| hasResponseStatus() || mavContainer.isRequestHandled())) {
mavContainer.setRequestHandled(true); return;
}
mavContainer.setRequestHandled(false);
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
}9.3 完整调用链
DispatcherServlet.doDispatch()
-> RequestMappingHandlerAdapter.handleInternal()
-> invokeHandlerMethod()
-> ServletInvocableHandlerMethod.invokeAndHandle()
-> HandlerMethodReturnValueHandlerComposite.handleReturnValue()
-> selectHandler()
-> handler.handleReturnValue()
-> writeWithMessageConverters()9.4 自定义返回值处理器实战
java
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME)
public @interface Encrypted {}
public class EncryptedReturnValueHandler implements HandlerMethodReturnValueHandler {
private final EncryptionService encryptionService;
public EncryptedReturnValueHandler(EncryptionService s) { this.encryptionService = s; }
@Override public boolean supportsReturnType(MethodParameter rt) { return rt.hasMethodAnnotation(Encrypted.class); }
@Override
public void handleReturnValue(Object rv, MethodParameter rt, ModelAndViewContainer mvc,
NativeWebRequest nwr) throws Exception {
mvc.setRequestHandled(true);
HttpServletResponse resp = ((ServletWebRequest) nwr).getResponse();
resp.setContentType(MediaType.TEXT_PLAIN_VALUE);
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write(encryptionService.encrypt(new ObjectMapper().writeValueAsString(rv)));
resp.getWriter().flush();
}
}十、异常处理与返回值
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ApiResponse<Void> handleBusiness(BusinessException ex) { return ApiResponse.error(ex.getCode(), ex.getMessage()); }
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public ResponseEntity<ApiResponse<Void>> handleNotAcceptable() {
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(ApiResponse.error(406, "不支持的响应格式"));
}
@ExceptionHandler(HttpMessageNotWritableException.class)
public ResponseEntity<ApiResponse<Void>> handleNotWritable() {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ApiResponse.error(500, "响应写入失败"));
}
}十一、性能优化与最佳实践
java
// 转换器性能优化
@Configuration
public class ConverterOptimizationConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> c : converters) {
if (c instanceof MappingJackson2HttpMessageConverter) {
ObjectMapper m = ((MappingJackson2HttpMessageConverter) c).getObjectMapper();
m.disable(SerializationFeature.INDENT_OUTPUT);
m.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
m.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
}
}
}
// 避免重复包装
@ControllerAdvice
public class EfficientResponseBodyAdvice implements ResponseBodyAdvice<Object> {
static final List<Class<?>> SKIP = Arrays.asList(byte[].class, InputStream.class,
Resource.class, StreamingResponseBody.class, ResponseBodyEmitter.class);
@Override public boolean supports(MethodParameter rt, Class<? extends HttpMessageConverter<?>> ct) {
for (Class<?> s : SKIP) if (s.isAssignableFrom(rt.getParameterType())) return false;
return MappingJackson2HttpMessageConverter.class.isAssignableFrom(ct);
}
@Override public Object beforeBodyWrite(Object body, MethodParameter rt, MediaType mct,
Class<? extends HttpMessageConverter<?>> ct, ServerHttpRequest req, ServerHttpResponse res) {
return body instanceof ApiResponse ? body : ApiResponse.success(body);
}
}11.1 调试日志
properties
logging.level.org.springframework.web.servlet.mvc.method.annotation=TRACE
logging.level.org.springframework.http.converter=TRACE十二、常见问题排查
| 问题 | 原因 | 方案 |
|---|---|---|
| 返回 406 | 无合适转换器 | 检查 Jackson 依赖、Accept 头 |
| 日期格式错误 | 格式器未注册 | 配置 spring.jackson.date-format |
| 循环引用 OOM | 双向引用 | 使用 @JsonIgnore、@JsonIdentityInfo |
| 敏感字段泄露 | 缺过滤配置 | 使用 @JsonIgnore、@JsonView |
| 自定义转换器无效 | 注册顺序 | 添加到列表最前面 |
| Advice 不执行 | supports 返回 false | 检查类型判断逻辑 |
十三、总结
Spring MVC 返回值处理机制的核心组件:
- HandlerMethodReturnValueHandler 体系:通过组合模式统一调度,支持自定义扩展。
- @ResponseBody 处理:
RequestResponseBodyMethodProcessor执行内容协商、转换器匹配、Advice 拦截、写入响应。 - HttpMessageConverter 链:模板方法模式,
GenericHttpMessageConverter支持泛型。 - MappingJackson2HttpMessageConverter:通过 ObjectMapper 完成 JSON 序列化。
- writeWithMessageConverters:核心调度方法,统筹类型提取、内容协商、转换器匹配和写入。
- 自定义序列化:多种机制实现灵活的数据输出控制。
源码路径汇总
| 组件 | 源码位置 |
|---|---|
| HandlerMethodReturnValueHandler | org.springframework.web.method.support |
| HandlerMethodReturnValueHandlerComposite | org.springframework.web.method.support |
| RequestMappingHandlerAdapter | org.springframework.web.servlet.mvc.method.annotation |
| RequestResponseBodyMethodProcessor | org.springframework.web.servlet.mvc.method.annotation |
| AbstractMessageConverterMethodProcessor | org.springframework.web.servlet.mvc.method.annotation |
| ServletInvocableHandlerMethod | org.springframework.web.servlet.mvc.method.annotation |
| HttpMessageConverter | org.springframework.http.converter |
| AbstractHttpMessageConverter | org.springframework.http.converter |
| AbstractJackson2HttpMessageConverter | org.springframework.http.converter.json |
| MappingJackson2HttpMessageConverter | org.springframework.http.converter.json |
| ResponseBodyAdvice | org.springframework.web.servlet.mvc.method.annotation |