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、二进制)