Spring MVC 返回 JSON 详解:从原理到自定义配置 在前后端分离架构中,Spring MVC 需频繁返回 JSON 格式数据(而非传统视图),核心依赖 @ResponseBody 注解与 HttpMessageConverter 机制。从 “快速实现→底层原理→自定义消息转换” 三个维度,彻底讲透 Spring MVC 如何处理 JSON 数据交互。
快速实现:返回 JSON 数据的 2 个核心步骤 Spring MVC 返回 JSON 只需 “添加依赖 + 标注注解 ”,无需复杂配置,适用于 90% 以上的常规场景。
步骤 1:添加 JSON 解析依赖(Jackson) Spring MVC 默认使用 Jackson 作为 JSON 解析工具,需添加 jackson-databind 依赖(包含 jackson-core 和 jackson-annotations):
1 2 3 4 5 6 <dependency > <groupId > com.fasterxml.jackson.core</groupId > <artifactId > jackson-databind</artifactId > <version > 2.15.2</version > </dependency >
依赖作用 :提供 JSON 与 Java 对象的互转能力,Spring MVC 会自动检测该依赖,并注册 MappingJackson2HttpMessageConverter(JSON 消息转换器)。
步骤 2:使用 @ResponseBody 标注方法 在 Controller 方法上添加 @ResponseBody 注解,Spring MVC 会自动将方法返回的 Java 对象(如 User、List<User>)转换为 JSON 字符串,并设置响应头 Content-Type: application/json。
实战示例:返回单个对象与集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.ResponseBody;import java.util.Arrays;import java.util.List;@Controller public class JsonController { @GetMapping("/getUser") @ResponseBody public User getUser () { User user = new User(); user.setId(1 ); user.setName("张三" ); user.setAge(25 ); user.setGender("男" ); return user; } @GetMapping("/getUserList") @ResponseBody public List<User> getUserList () { User user1 = new User(2 , "李四" , 30 , "女" ); User user2 = new User(3 , "王五" , 28 , "男" ); return Arrays.asList(user1, user2); } @GetMapping("/getDefaultJson") public String getDefaultJson () { return "{\"message\":\"使用 @RestController 简化配置\"}" ; } static class User { private Integer id; private String name; private Integer age; private String gender; public User () {} public User (Integer id, String name, Integer age, String gender) { this .id = id; this .name = name; this .age = age; this .gender = gender; } } }
响应结果示例:
简化配置:@RestController 注解 Spring 4.0+ 提供 @RestController 注解,它是 @Controller 与 @ResponseBody 的组合注解,类中所有方法默认返回 JSON ,无需逐个添加 @ResponseBody:
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.springframework.web.bind.annotation.RestController;import org.springframework.web.bind.annotation.GetMapping;@RestController public class RestJsonController { @GetMapping("/restUser") public User getRestUser () { return new User(4 , "赵六" , 22 , "男" ); } }
底层原理:HttpMessageConverter 消息转换机制 为什么添加 @ResponseBody 就能返回 JSON?核心是 Spring MVC 的 HttpMessageConverter 接口 ,它负责 “请求消息→Java 对象” 和 “Java 对象→响应消息” 的转换,是前后端数据交互的桥梁。
1. HttpMessageConverter 核心接口定义 HttpMessageConverter 接口包含 5 个核心方法,按功能分为 “转换能力判断” 和 “实际转换操作”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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, HttpMessageNotReadableException ; void write (T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException ;}
关键概念 :MediaType 代表媒体类型(如 application/json 表示 JSON 格式,text/html 表示 HTML 格式),用于匹配请求 / 响应的 Content-Type 头。
2. JSON 转换的核心实现:MappingJackson2HttpMessageConverter 当添加 jackson-databind 依赖后,Spring MVC 会自动注册 MappingJackson2HttpMessageConverter,它是处理 JSON 的核心转换器,工作流程如下:
(1)@ResponseBody 触发转换 当方法标注 @ResponseBody 时,Spring MVC 会:
调用 HandlerAdapter(如 RequestMappingHandlerAdapter)执行目标方法,获取返回的 Java 对象(如 User);
遍历所有 HttpMessageConverter,找到能处理该 Java 对象且支持 application/json 媒体类型的转换器(即 MappingJackson2HttpMessageConverter);
调用 MappingJackson2HttpMessageConverter.write() 方法,将 Java 对象转换为 JSON 字符串;
设置响应头 Content-Type: application/json,并将 JSON 字符串写入响应体。
(2)MappingJackson2HttpMessageConverter 核心逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter { public MappingJackson2HttpMessageConverter (ObjectMapper objectMapper) { super (objectMapper, MediaType.APPLICATION_JSON, new MediaType("application" , "*+json" )); } @Override public boolean canWrite (Class<?> clazz, MediaType mediaType) { return super .canWrite(clazz, mediaType) && !clazz.isAssignableFrom(MultiValueMap.class); } @Override protected void writeInternal (Object object, Type type, HttpOutputMessage outputMessage) throws IOException { MediaType contentType = outputMessage.getHeaders().getContentType(); JsonEncoding encoding = getJsonEncoding(contentType); ObjectMapper objectMapper = getObjectMapper(); try (OutputStream outputStream = outputMessage.getBody()) { JsonGenerator generator = objectMapper.getFactory().createGenerator(outputStream, encoding); writePrefix(generator, object); if (type != null && TypeUtils.isAssignable(type, object.getClass())) { objectMapper.writerFor(type).writeValue(generator, object); } else { objectMapper.writeValue(generator, object); } writeSuffix(generator, object); generator.flush(); } } }
3. 其他常用 HttpMessageConverter Spring MVC 内置多种消息转换器,覆盖不同数据格式:
转换器类名
支持的媒体类型
功能描述
MappingJackson2HttpMessageConverter
application/json、application/*+json
Java 对象 ↔ JSON 字符串
StringHttpMessageConverter
text/plain、/
字符串 ↔ 文本响应(默认编码 ISO-8859-1,需注意中文乱码)
ByteArrayHttpMessageConverter
application/octet-stream
字节数组 ↔ 二进制响应(如下载文件)
SourceHttpMessageConverter
application/xml、text/xml
DOM/SAX Source ↔ XML 字符串
高级配置:自定义 JSON 转换规则 默认的 JSON 转换可能无法满足需求(如日期格式统一、忽略 null 字段、自定义属性名),可通过配置 ObjectMapper(Jackson 的核心工具类)实现自定义规则。
1. 全局配置:统一 JSON 转换规则 通过 WebMvcConfigurer 的 extendMessageConverters 方法,修改 MappingJackson2HttpMessageConverter 的 ObjectMapper:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.SerializationFeature;import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;import org.springframework.context.annotation.Configuration;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;import java.util.List;@Configuration public class WebMvcJsonConfig implements WebMvcConfigurer { @Override public void extendMessageConverters (List<HttpMessageConverter<?>> converters) { for (HttpMessageConverter<?> converter : converters) { if (converter instanceof MappingJackson2HttpMessageConverter) { MappingJackson2HttpMessageConverter jsonConverter = (MappingJackson2HttpMessageConverter) converter; ObjectMapper objectMapper = new ObjectMapper(); JavaTimeModule timeModule = new JavaTimeModule(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ); timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); objectMapper.registerModule(timeModule); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); jsonConverter.setObjectMapper(objectMapper); break ; } } } }
配置效果:
日期 LocalDateTime.of(2024, 5, 20, 14, 30) 转为 "2024-05-20 14:30:00";
Java 字段 userName 转为 JSON 字段 user_name;
忽略值为 null 的字段(如 gender: null 不会出现在 JSON 中)。
2. 局部配置:方法级自定义规则 通过 @JsonFormat、@JsonIgnore 等 Jackson 注解,在 Java 类或字段上单独配置转换规则,优先级高于全局配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import com.fasterxml.jackson.annotation.JsonFormat;import com.fasterxml.jackson.annotation.JsonIgnore;import com.fasterxml.jackson.annotation.JsonProperty;import java.time.LocalDateTime;public class User { private Integer id; @JsonProperty("user_name") private String name; @JsonIgnore private Integer age; @JsonFormat(pattern = "yyyy/MM/dd", timezone = "GMT+8") private LocalDateTime createTime; }
响应结果示例: 1 2 3 4 5 6 { "id" : 1 , "user_name" : "张三" , "createTime" : "2024/05/20" }
自定义 HttpMessageConverter 当默认转换器无法满足特殊格式需求(如自定义二进制格式、XML 变体)时,可通过继承 AbstractHttpMessageConverter 实现自定义转换器。
示例:自定义 CSV 格式转换器 实现一个将 List<User> 转为 CSV 字符串的转换器:
步骤 1:实现自定义转换器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import org.springframework.http.HttpInputMessage;import org.springframework.http.HttpOutputMessage;import org.springframework.http.MediaType;import org.springframework.http.converter.AbstractHttpMessageConverter;import org.springframework.http.converter.HttpMessageNotReadableException;import org.springframework.http.converter.HttpMessageNotWritableException;import java.io.IOException;import java.io.OutputStreamWriter;import java.nio.charset.StandardCharsets;import java.util.List;public class CsvMessageConverter extends AbstractHttpMessageConverter <List <User >> { public CsvMessageConverter () { super (new MediaType("text" , "csv" , StandardCharsets.UTF_8)); } @Override protected boolean canRead (Class<?> clazz, MediaType mediaType) { return false ; } @Override protected boolean canWrite (Class<?> clazz, MediaType mediaType) { return List.class.isAssignableFrom(clazz) && User.class.isAssignableFrom(clazz.getComponentType()); } @Override protected List<User> readInternal (Class<? extends List<User>> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { throw new UnsupportedOperationException("不支持 CSV 读取" ); } @Override protected void writeInternal (List<User> users, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { String header = "id,name,age,gender\n" ; try (OutputStreamWriter writer = new OutputStreamWriter(outputMessage.getBody(), StandardCharsets.UTF_8)) { writer.write(header); for (User user : users) { String line = String.format("%d,%s,%d,%s\n" , user.getId(), user.getName(), user.getAge(), user.getGender()); writer.write(line); } } } }
步骤 2:注册自定义转换器 1 2 3 4 5 6 7 8 9 @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void extendMessageConverters (List<HttpMessageConverter<?>> converters) { converters.add(new CsvMessageConverter()); } }
步骤 3:使用自定义转换器 通过 produces 属性指定响应媒体类型为 text/csv,触发自定义转换器:
1 2 3 4 5 6 7 8 9 10 11 @RestController public class CsvController { @GetMapping(value = "/getUserCsv", produces = "text/csv") public List<User> getUserCsv () { User user1 = new User(1 , "张三" , 25 , "男" ); User user2 = new User(2 , "李四" , 30 , "女" ); return Arrays.asList(user1, user2); } }
响应结果(CSV 格式): 1 2 3 id,name,age,gender 1,张三,25,男 2,李四,30,女
常见问题与解决方案
问题现象
可能原因
解决方案
JSON 中文乱码
StringHttpMessageConverter 默认编码为 ISO-8859-1
配置 StringHttpMessageConverter 编码为 UTF-8
日期格式显示为时间戳
Jackson 默认将日期转为时间戳
注册 JavaTimeModule,统一日期格式
null 字段出现在 JSON 中
默认不忽略 null 字段
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)
自定义转换器不生效
1. 未注册转换器;2. canWrite 方法返回 false
1. 在 extendMessageConverters 中注册;2. 检查 canWrite 逻辑
总结 Spring MVC 返回 JSON 的核心是 HttpMessageConverter 机制,关键要点如下:
快速实现 :添加 jackson-databind 依赖 + 标注 @ResponseBody(或使用 @RestController);
底层原理 :MappingJackson2HttpMessageConverter 利用 Jackson 将 Java 对象转为 JSON,@ResponseBody 触发转换流程;
自定义配置 :通过 ObjectMapper 统一 JSON 规则(日期、字段名、null 处理),或通过 Jackson 注解实现局部配置;
扩展能力 :继承 AbstractHttpMessageConverter 实现自定义格式转换器(如 CSV、二进制)
v1.3.10