0%

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-corejackson-annotations):

1
2
3
4
5
6
<!-- 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 对象(如 UserList<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 {

// 1. 返回单个 User 对象(转为 JSON 对象)
@GetMapping("/getUser")
@ResponseBody // 核心注解:将返回值转为 JSON
public User getUser() {
User user = new User();
user.setId(1);
user.setName("张三");
user.setAge(25);
user.setGender("男");
return user;
}

// 2. 返回 List<User> 集合(转为 JSON 数组)
@GetMapping("/getUserList")
@ResponseBody
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)
@GetMapping("/getDefaultJson")
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 对象:

    1
    { "id": 1, "name": "张三", "age": 25, "gender": "男" }
  • 访问/getUserList,响应 JSON 数组:

    1
    2
    3
    4
    [
    { "id": 2, "name": "李四", "age": 30, "gender": "女" },
    { "id": 3, "name": "王五", "age": 28, "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 = @Controller + @ResponseBody
@RestController
public class RestJsonController {

// 无需添加 @ResponseBody,默认返回 JSON
@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> {

// 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 会:

  1. 调用 HandlerAdapter(如 RequestMappingHandlerAdapter)执行目标方法,获取返回的 Java 对象(如 User);
  2. 遍历所有 HttpMessageConverter,找到能处理该 Java 对象且支持 application/json 媒体类型的转换器(即 MappingJackson2HttpMessageConverter);
  3. 调用 MappingJackson2HttpMessageConverter.write() 方法,将 Java 对象转换为 JSON 字符串;
  4. 设置响应头 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 {

// 构造器:默认支持 application/json、application/*+json 媒体类型
public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}

// 判断是否能写入:支持所有非 null 的 Java 对象
@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return super.canWrite(clazz, mediaType) && !clazz.isAssignableFrom(MultiValueMap.class);
}

// 写入逻辑:使用 Jackson 的 ObjectMapper 将 Java 对象转为 JSON
@Override
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 转换规则

通过 WebMvcConfigurerextendMessageConverters 方法,修改 MappingJackson2HttpMessageConverterObjectMapper

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 {

// 自定义 JSON 转换规则(不覆盖默认转换器,仅修改 MappingJackson2HttpMessageConverter)
@Override
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 类或字段上单独配置转换规则,优先级高于全局配置:

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;

// 局部自定义:JSON 字段名改为 "user_name"(覆盖全局的 snake_case)
@JsonProperty("user_name")
private String name;

// 局部自定义:忽略该字段(不转为 JSON)
@JsonIgnore
private Integer age;

// 局部自定义:日期格式改为 "yyyy/MM/dd"(覆盖全局的 "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy/MM/dd", timezone = "GMT+8")
private LocalDateTime createTime;

// getter/setter
}
响应结果示例:
1
2
3
4
5
6
{
"id": 1,
"user_name": "张三", // 自定义字段名
"createTime": "2024/05/20" // 自定义日期格式
// age 字段被忽略,未出现
}

自定义 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;

// 自定义 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)
@Override
protected boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}

// 3. 判断是否能写入(仅支持 List<User> 类型)
@Override
protected boolean canWrite(Class<?> clazz, MediaType mediaType) {
return List.class.isAssignableFrom(clazz) && User.class.isAssignableFrom(clazz.getComponentType());
}

// 4. 读取逻辑(本例不支持,抛出异常)
@Override
protected List<User> readInternal(Class<? extends List<User>> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
throw new UnsupportedOperationException("不支持 CSV 读取");
}

// 5. 写入逻辑:List<User> → CSV 字符串
@Override
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:注册自定义转换器
1
2
3
4
5
6
7
8
9
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// 添加自定义 CSV 转换器(不会覆盖默认转换器)
converters.add(new CsvMessageConverter());
}
}
步骤 3:使用自定义转换器

通过 produces 属性指定响应媒体类型为 text/csv,触发自定义转换器:

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class CsvController {

// produces = "text/csv":指定响应为 CSV 格式,触发 CsvMessageConverter
@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 机制,关键要点如下:

  1. 快速实现:添加 jackson-databind 依赖 + 标注 @ResponseBody(或使用 @RestController);
  2. 底层原理MappingJackson2HttpMessageConverter 利用 Jackson 将 Java 对象转为 JSON,@ResponseBody 触发转换流程;
  3. 自定义配置:通过 ObjectMapper 统一 JSON 规则(日期、字段名、null 处理),或通过 Jackson 注解实现局部配置;
  4. 扩展能力:继承 AbstractHttpMessageConverter 实现自定义格式转换器(如 CSV、二进制)

欢迎关注我的其它发布渠道