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

<!-- 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

实战示例:返回单个对象与集合
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 对象:

    { "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
@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 个核心方法,按功能分为 “转换能力判断” 和 “实际转换操作”:

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 核心逻辑
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

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 类或字段上单独配置转换规则,优先级高于全局配置:

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
}
响应结果示例:
{
  "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)
    @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:注册自定义转换器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

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

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

@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 格式):
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、二进制)