1. Spring MVC 4.0中返回JSON的基础配置在Spring MVC 4.0中返回JSON数据是构建现代Web应用的基础能力。默认情况下Spring Boot会自动配置Jackson作为JSON处理器这得益于spring-boot-starter-web依赖中内置的Jackson支持。要让控制器方法返回JSON最简单的做法是在方法上添加ResponseBody注解或者直接在类级别使用RestController。以下是一个基本示例RestController RequestMapping(/api/users) public class UserController { GetMapping(/{id}) public User getUser(PathVariable Long id) { return new User(id, 张三, zhangsanexample.com); } }这个简单的控制器会自动将User对象序列化为JSON返回。Spring MVC通过MappingJackson2HttpMessageConverter完成这个转换过程。提示在Spring Boot 4.x中Jackson 3已成为默认JSON处理器。如果项目仍在使用Jackson 2建议尽快升级因为Spring Boot将在未来版本中移除对Jackson 2的支持。2. 自定义JSON序列化配置2.1 全局Jackson配置我们可以在application.properties或application.yml中配置Jackson的全局行为# 日期格式配置 spring.jackson.date-formatyyyy-MM-dd HH:mm:ss spring.jackson.time-zoneGMT8 # 空值处理 spring.jackson.default-property-inclusionnon_null # 美化输出开发环境使用 spring.jackson.serialization.indent-outputtrue2.2 自定义ObjectMapper对于更复杂的配置需求可以创建自定义的ObjectMapper BeanConfiguration public class JacksonConfig { Bean public ObjectMapper objectMapper() { ObjectMapper mapper new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return mapper; } }2.3 使用Json注解控制字段序列化在实体类上我们可以使用Jackson的各种注解精细控制序列化行为public class Product { JsonIgnore private Long internalId; JsonProperty(product_name) private String name; JsonFormat(pattern yyyy-MM-dd) private Date productionDate; JsonInclude(Include.NON_EMPTY) private ListString tags; }3. 高级JSON处理技巧3.1 处理泛型集合返回当需要返回泛型集合时直接返回List可能会导致类型信息丢失。推荐使用ResponseEntity包装响应GetMapping public ResponseEntityListUser getAllUsers() { ListUser users userService.findAll(); return ResponseEntity.ok() .header(X-Total-Count, String.valueOf(users.size())) .body(users); }3.2 自定义序列化器/反序列化器对于特殊类型的序列化需求可以创建自定义的JsonSerializer和JsonDeserializerpublic class MoneySerializer extends JsonSerializerMoney { Override public void serialize(Money value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeStartObject(); gen.writeNumberField(amount, value.getAmount()); gen.writeStringField(currency, value.getCurrency().getCurrencyCode()); gen.writeEndObject(); } }然后通过模块注册Bean public Module moneyModule() { SimpleModule module new SimpleModule(); module.addSerializer(Money.class, new MoneySerializer()); return module; }3.3 使用JsonView控制不同场景的字段输出对于同一个对象在不同接口中需要返回不同字段的场景可以使用JsonViewpublic class View { public interface Public {} public interface Internal extends Public {} } public class Article { JsonView(View.Public.class) private String title; JsonView(View.Internal.class) private String draftContent; } RestController public class ArticleController { JsonView(View.Public.class) GetMapping(/public/articles/{id}) public Article getPublicArticle(PathVariable Long id) { return articleService.findById(id); } JsonView(View.Internal.class) GetMapping(/internal/articles/{id}) public Article getInternalArticle(PathVariable Long id) { return articleService.findById(id); } }4. 异常处理与统一响应格式4.1 统一响应结构在实际项目中我们通常会定义统一的响应格式public class ApiResponseT { private boolean success; private String code; private String message; private T data; private long timestamp; // 构造方法、getter/setter省略 }然后通过ControllerAdvice统一处理RestControllerAdvice public class ResponseWrapperAdvice implements ResponseBodyAdviceObject { Override public boolean supports(MethodParameter returnType, Class? extends HttpMessageConverter? converterType) { return true; } Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class? extends HttpMessageConverter? selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof ApiResponse) { return body; } return ApiResponse.success(body); } }4.2 异常统一处理ExceptionHandler(BusinessException.class) public ResponseEntityApiResponseVoid handleBusinessException(BusinessException ex) { ApiResponseVoid response ApiResponse.fail(ex.getErrorCode(), ex.getMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } ExceptionHandler(Exception.class) public ResponseEntityApiResponseVoid handleException(Exception ex) { ApiResponseVoid response ApiResponse.fail(SYSTEM_ERROR, 系统繁忙请稍后再试); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); }5. 性能优化与最佳实践5.1 启用HTTP压缩在application.properties中启用GZIP压缩可以显著减少JSON数据大小server.compression.enabledtrue server.compression.mime-typesapplication/json,application/xml,text/html,text/xml,text/plain server.compression.min-response-size10245.2 使用DTO替代直接返回实体避免直接返回JPA实体而是使用专门的DTO类Data public class UserDto { private Long id; private String name; private String email; public static UserDto fromEntity(User user) { UserDto dto new UserDto(); dto.setId(user.getId()); dto.setName(user.getFirstName() user.getLastName()); dto.setEmail(user.getEmail()); return dto; } }5.3 分页查询的JSON响应对于分页查询推荐返回标准化的分页结构GetMapping public PageResponseUserDto getUsers( RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int size) { PageUser userPage userService.findAll(PageRequest.of(page - 1, size)); return PageResponse.of(userPage.map(UserDto::fromEntity)); } public class PageResponseT { private ListT content; private int currentPage; private int pageSize; private long totalElements; private int totalPages; // 静态工厂方法等省略 }5.4 使用ETag实现缓存通过ETag实现客户端缓存GetMapping(/{id}) public ResponseEntityUserDto getUser(PathVariable Long id) { User user userService.findById(id); String etag DigestUtils.md5DigestAsHex(user.getVersion().toString().getBytes()); return ResponseEntity.ok() .eTag(etag) .cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS)) .body(UserDto.fromEntity(user)); }6. 测试JSON接口6.1 使用MockMvc测试控制器SpringBootTest AutoConfigureMockMvc class UserControllerTest { Autowired private MockMvc mockMvc; Test void shouldReturnUser() throws Exception { mockMvc.perform(get(/api/users/1) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath($.name).value(张三)) .andExpect(jsonPath($.email).exists()); } }6.2 使用TestRestTemplate测试完整HTTP栈SpringBootTest(webEnvironment WebEnvironment.RANDOM_PORT) class UserControllerIntegrationTest { Autowired private TestRestTemplate restTemplate; Test void shouldReturnUser() { ResponseEntityUserDto response restTemplate.getForEntity( /api/users/1, UserDto.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody().getName()).isEqualTo(张三); } }6.3 使用JSONAssert进行JSON比较Test void testJsonResponse() throws Exception { String actual mockMvc.perform(get(/api/users/1)) .andReturn().getResponse().getContentAsString(); String expected {\name\:\张三\,\email\:\zhangsanexample.com\}; JSONAssert.assertEquals(expected, actual, false); }7. 常见问题与解决方案7.1 处理循环引用当JPA实体存在双向关联时JSON序列化可能出现循环引用问题。解决方案使用JsonIgnore忽略一方OneToMany(mappedBy department) JsonIgnore private ListEmployee employees;使用JsonManagedReference和JsonBackReference// 在主方 OneToMany(mappedBy department) JsonManagedReference private ListEmployee employees; // 在从方 ManyToOne JoinColumn(name department_id) JsonBackReference private Department department;使用DTO模式彻底避免实体直接序列化7.2 处理LocalDateTime序列化Java 8日期类型需要额外配置添加依赖dependency groupIdcom.fasterxml.jackson.datatype/groupId artifactIdjackson-datatype-jsr310/artifactId /dependency配置ObjectMapperObjectMapper mapper new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);7.3 大数字精度问题JavaScript无法安全表示Java的Long型最大值可能导致精度丢失。解决方案配置Jackson将长数字序列化为字符串JsonFormat(shape JsonFormat.Shape.STRING) private Long id;或者全局配置mapper.configure(DeserializationFeature.USE_LONG_FOR_INTS, true);7.4 统一处理null字段默认情况下Jackson会输出所有字段包括null值。可以通过以下方式统一处理全局配置spring.jackson.default-property-inclusionnon_null或在类级别JsonInclude(Include.NON_NULL) public class MyDto { // ... }8. Spring MVC JSON处理原理剖析8.1 消息转换器机制Spring MVC通过HttpMessageConverter接口实现请求/响应的转换。对于JSON处理主要涉及MappingJackson2HttpMessageConverter处理Jackson的JSON转换GsonHttpMessageConverter处理Gson的JSON转换JsonbHttpMessageConverter处理JSON-B的转换这些转换器会根据请求的Accept头或produces属性自动选择。8.2 内容协商策略Spring MVC通过ContentNegotiationManager决定返回的内容类型。默认策略是检查URL后缀如.json检查请求的Accept头检查默认内容类型通常为JSON可以通过配置修改策略Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { configurer.favorParameter(true) .parameterName(format) .ignoreAcceptHeader(false) .defaultContentType(MediaType.APPLICATION_JSON) .mediaType(json, MediaType.APPLICATION_JSON) .mediaType(xml, MediaType.APPLICATION_XML); }8.3 Jackson自动配置原理Spring Boot的Jackson自动配置主要通过JacksonAutoConfiguration完成它会自动检测类路径下的Jackson库配置默认的ObjectMapper注册所有标有JsonComponent的组件应用application.properties中的配置注册所有发现的Jackson模块可以通过定义自己的Jackson2ObjectMapperBuilderCustomizerBean来进一步定制Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { return builder - { builder.simpleDateFormat(yyyy-MM-dd HH:mm:ss); builder.modules(new MyCustomModule()); }; }9. 实际项目中的JSON实践9.1 API版本控制当API需要支持多版本时可以通过内容协商实现GetMapping(value /users/{id}, produces application/vnd.company.app-v1json) public UserV1 getUserV1(PathVariable Long id) { return userService.getUserV1(id); } GetMapping(value /users/{id}, produces application/vnd.company.app-v2json) public UserV2 getUserV2(PathVariable Long id) { return userService.getUserV2(id); }9.2 数据脱敏处理对于敏感信息可以在序列化时进行脱敏public class SensitiveDataSerializer extends JsonSerializerString { Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { if (value null) { gen.writeNull(); return; } gen.writeString(value.substring(0, 3) **** value.substring(value.length() - 2)); } } // 使用 public class UserDto { JsonSerialize(using SensitiveDataSerializer.class) private String idCard; }9.3 多语言支持结合Spring的国际化支持返回多语言JSONRestController public class MessageController { Autowired private MessageSource messageSource; GetMapping(/greeting) public MapString, String greeting(RequestHeader(Accept-Language) Locale locale) { String greeting messageSource.getMessage(greeting, null, locale); return Collections.singletonMap(message, greeting); } }9.4 动态字段控制通过JsonFilter实现动态字段过滤JsonFilter(dynamicFilter) public class Product { private Long id; private String name; private BigDecimal price; // getters/setters } GetMapping(/products/{id}) public MappingJacksonValue getProduct(PathVariable Long id, RequestParam(required false) String fields) { Product product productService.findById(id); MappingJacksonValue result new MappingJacksonValue(product); if (fields ! null) { FilterProvider filters new SimpleFilterProvider() .addFilter(dynamicFilter, SimpleBeanPropertyFilter.filterOutAllExcept(fields.split(,))); result.setFilters(filters); } return result; }10. 未来趋势与替代方案10.1 JSON序列化性能比较在选择JSON库时性能是一个重要考量因素。以下是常见Java JSON库的简单比较库序列化速度反序列化速度内存占用灵活性Jackson快快低高Gson中等中等中等中等Fastjson最快最快低低JSON-B慢慢高高10.2 替代数据格式除了JSON现代Web API也开始采用其他数据格式Protocol BuffersGoogle开发的高效二进制格式MessagePack类似JSON但更紧凑的二进制格式BSONMongoDB使用的二进制JSON扩展Spring MVC也支持这些格式可以通过添加相应的HttpMessageConverter实现。10.3 GraphQL替代REST对于复杂的数据需求GraphQL提供了更灵活的查询方式。Spring for GraphQL项目提供了完善的集成支持Controller public class UserController { QueryMapping public User user(Argument Long id) { return userService.findById(id); } SchemaMapping(typeName User, field posts) public ListPost posts(User user) { return postService.findByUserId(user.getId()); } }10.4 RSocket与响应式JSON在响应式应用中RSocket提供了更高效的通信方式Controller public class UserRSocketController { MessageMapping(users.{id}) public MonoUser getUser(DestinationVariable Long id) { return userService.findByIdReactive(id); } MessageMapping(users) public FluxUser getUsers() { return userService.findAllReactive(); } }