springmvc返回json
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):
<!-- Maven 依赖 -->
<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。
实战示例:返回单个对象与集合
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;
public class JsonController {
// 1. 返回单个 User 对象(转为 JSON 对象)
// 核心注解:将返回值转为 JSON
public User getUser() {
User user = new User();
user.setId(1);
user.setName("张三");
user.setAge(25);
user.setGender("男");
return user;
}
// 2. 返回 List<User> 集合(转为 JSON 数组)
public List<User> getUserList() {
User user1 = new User(2, "李四", 30, "女");
User user2 = new User(3, "王五", 28, "男");
return Arrays.asList(user1, user2);
}
// 3. 简化:使用 @RestController 替代 @Controller + @ResponseBody
// @RestController = @Controller + @ResponseBody(类中所有方法默认返回 JSON)
public String getDefaultJson() {
return "{\"message\":\"使用 @RestController 简化配置\"}"; // 直接返回 JSON 字符串
}
// 自定义 User 类(需提供 getter/setter,Jackson 通过 getter 取值)
static class User {
private Integer id;
private String name;
private Integer age;
private String gender;
// 无参构造器(Jackson 反射创建对象必需)
public User() {}
public User(Integer id, String name, Integer age, String gender) {
this.id = id;
this.name = name;
this.age = age;
this.gender = gender;
}
// getter/setter(省略,需手动添加)
}
}
响应结果示例:
访问/getUser,响应 JSON 对象:
{ "id": 1, "name": "张三", "age": 25, "gender": "男" }访问/getUserList,响应 JSON 数组:
[ { "id": 2, "name": "李四", "age": 30, "gender": "女" }, { "id": 3, "name": "王五", "age": 28, "gender": "男" } ]
简化配置:@RestController 注解
Spring 4.0+ 提供 @RestController 注解,它是 @Controller 与 @ResponseBody 的组合注解,类中所有方法默认返回 JSON,无需逐个添加 @ResponseBody:
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
// @RestController = @Controller + @ResponseBody
public class RestJsonController {
// 无需添加 @ResponseBody,默认返回 JSON
public User getRestUser() {
return new User(4, "赵六", 22, "男");
}
}
底层原理:HttpMessageConverter 消息转换机制
为什么添加 @ResponseBody 就能返回 JSON?核心是 Spring MVC 的 HttpMessageConverter 接口,它负责 “请求消息→Java 对象” 和 “Java 对象→响应消息” 的转换,是前后端数据交互的桥梁。
1. HttpMessageConverter 核心接口定义
HttpMessageConverter 接口包含 5 个核心方法,按功能分为 “转换能力判断” 和 “实际转换操作”:
public interface HttpMessageConverter<T> {
// 1. 判断是否能读取指定类型(请求消息→Java 对象)
boolean canRead(Class<?> clazz, MediaType mediaType);
// 2. 判断是否能写入指定类型(Java 对象→响应消息)
boolean canWrite(Class<?> clazz, MediaType mediaType);
// 3. 返回当前转换器支持的媒体类型(如 application/json、text/plain)
List<MediaType> getSupportedMediaTypes();
// 4. 读取请求消息,转换为 Java 对象
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException;
// 5. 将 Java 对象写入响应消息
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 核心逻辑
public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
// 构造器:默认支持 application/json、application/*+json 媒体类型
public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
// 判断是否能写入:支持所有非 null 的 Java 对象
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return super.canWrite(clazz, mediaType) && !clazz.isAssignableFrom(MultiValueMap.class);
}
// 写入逻辑:使用 Jackson 的 ObjectMapper 将 Java 对象转为 JSON
protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException {
MediaType contentType = outputMessage.getHeaders().getContentType();
JsonEncoding encoding = getJsonEncoding(contentType);
ObjectMapper objectMapper = getObjectMapper();
// 使用 Jackson 转换 Java 对象为 JSON 并写入响应流
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:
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;
public class WebMvcJsonConfig implements WebMvcConfigurer {
// 自定义 JSON 转换规则(不覆盖默认转换器,仅修改 MappingJackson2HttpMessageConverter)
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 1. 遍历所有转换器,找到 MappingJackson2HttpMessageConverter
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jsonConverter = (MappingJackson2HttpMessageConverter) converter;
// 2. 创建自定义 ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
// 3. 配置 JSON 转换规则
// 3.1 统一日期格式(LocalDateTime 转为 "yyyy-MM-dd HH:mm:ss")
JavaTimeModule timeModule = new JavaTimeModule();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
objectMapper.registerModule(timeModule);
// 3.2 忽略值为 null 的字段
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 3.3 允许序列化空集合(默认不允许)
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// 3.4 自定义属性名(如 Java 字段 "userName" → JSON 字段 "user_name")
objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
// 4. 将自定义 ObjectMapper 设置到转换器
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 类或字段上单独配置转换规则,优先级高于全局配置:
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;
// 局部自定义:JSON 字段名改为 "user_name"(覆盖全局的 snake_case)
private String name;
// 局部自定义:忽略该字段(不转为 JSON)
private Integer age;
// 局部自定义:日期格式改为 "yyyy/MM/dd"(覆盖全局的 "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
// getter/setter
}
响应结果示例:
{
"id": 1,
"user_name": "张三", // 自定义字段名
"createTime": "2024/05/20" // 自定义日期格式
// age 字段被忽略,未出现
}
自定义 HttpMessageConverter
当默认转换器无法满足特殊格式需求(如自定义二进制格式、XML 变体)时,可通过继承 AbstractHttpMessageConverter 实现自定义转换器。
示例:自定义 CSV 格式转换器
实现一个将 List<User> 转为 CSV 字符串的转换器:
步骤 1:实现自定义转换器
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;
// 自定义 CSV 转换器:支持 List<User> → CSV 字符串
public class CsvMessageConverter extends AbstractHttpMessageConverter<List<User>> {
// 1. 构造器:指定支持的媒体类型(text/csv)和编码(UTF-8)
public CsvMessageConverter() {
super(new MediaType("text", "csv", StandardCharsets.UTF_8));
}
// 2. 判断是否能读取(本例仅支持写入,返回 false)
protected boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}
// 3. 判断是否能写入(仅支持 List<User> 类型)
protected boolean canWrite(Class<?> clazz, MediaType mediaType) {
return List.class.isAssignableFrom(clazz) && User.class.isAssignableFrom(clazz.getComponentType());
}
// 4. 读取逻辑(本例不支持,抛出异常)
protected List<User> readInternal(Class<? extends List<User>> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
throw new UnsupportedOperationException("不支持 CSV 读取");
}
// 5. 写入逻辑:List<User> → CSV 字符串
protected void writeInternal(List<User> users, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
// CSV 表头
String header = "id,name,age,gender\n";
// 写入 CSV 内容
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:注册自定义转换器
public class WebMvcConfig implements WebMvcConfigurer {
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 添加自定义 CSV 转换器(不会覆盖默认转换器)
converters.add(new CsvMessageConverter());
}
}
步骤 3:使用自定义转换器
通过 produces 属性指定响应媒体类型为 text/csv,触发自定义转换器:
public class CsvController {
// produces = "text/csv":指定响应为 CSV 格式,触发 CsvMessageConverter
public List<User> getUserCsv() {
User user1 = new User(1, "张三", 25, "男");
User user2 = new User(2, "李四", 30, "女");
return Arrays.asList(user1, user2);
}
}
响应结果(CSV 格式):
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、二进制)