Skip to content

Spring MVC Message Converters 深度解析 🚀

什么是 Message Converters?为什么需要它?

想象一下,你正在开发一个 RESTful API,客户端发送 JSON 数据,你的 Controller 需要接收 Kotlin 对象;同时,你的 Controller 返回 Kotlin 对象,客户端期望收到 JSON 响应。这个"魔法般"的转换过程就是由 HttpMessageConverter 完成的!

NOTE

HttpMessageConverter 是 Spring MVC 中负责在 HTTP 请求/响应与 Java/Kotlin 对象之间进行序列化和反序列化的核心组件。

核心痛点与解决方案

kotlin
@RestController
class UserController {
    
    @PostMapping("/users")
    fun createUser(request: HttpServletRequest): ResponseEntity<String> {
        // 😰 手动读取请求体
        val json = request.reader.readText()
        
        // 😰 手动解析 JSON
        val objectMapper = ObjectMapper()
        val user = objectMapper.readValue(json, User::class.java)
        
        // 业务逻辑处理...
        val savedUser = userService.save(user)
        
        // 😰 手动转换为 JSON 响应
        val responseJson = objectMapper.writeValueAsString(savedUser)
        return ResponseEntity.ok(responseJson) 
    }
}
kotlin
@RestController
class UserController {
    
    @PostMapping("/users")
    fun createUser(@RequestBody user: User): User { 
        // ✅ 自动将 JSON 转换为 User 对象
        // ✅ 自动将返回的 User 对象转换为 JSON
        return userService.save(user) 
    }
}

Message Converters 的工作原理

自定义 Message Converters 配置

基础配置示例

kotlin
@Configuration
class WebConfiguration : WebMvcConfigurer {

    override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
        // 创建自定义的 Jackson 配置
        val builder = Jackson2ObjectMapperBuilder()
            .indentOutput(true) 
            .dateFormat(SimpleDateFormat("yyyy-MM-dd")) 
            .modulesToInstall(ParameterNamesModule()) 
        
        // 添加 JSON 转换器
        converters.add(MappingJackson2HttpMessageConverter(builder.build()))
        
        // 添加 XML 转换器
        converters.add(MappingJackson2XmlHttpMessageConverter(
            builder.createXmlMapper(true).build()
        ))
    }
}

TIP

使用 configureMessageConverters()完全替换默认的转换器,而使用 extendMessageConverters() 则是在默认转换器基础上添加或修改

扩展现有转换器(推荐方式)

kotlin
@Configuration
class WebConfiguration : WebMvcConfigurer {

    override fun extendMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
        // 🎯 在现有转换器基础上进行扩展
        val jacksonConverter = converters
            .filterIsInstance<MappingJackson2HttpMessageConverter>()
            .firstOrNull()
        
        jacksonConverter?.let { converter ->
            val objectMapper = converter.objectMapper
            // 自定义 ObjectMapper 配置
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
        }
    }
}

实际业务场景应用

场景一:API 版本兼容性处理

kotlin
data class UserV1(
    val id: Long,
    val name: String,
    val email: String
)

data class UserV2(
    val id: Long,
    val fullName: String, // 字段名变更
    val email: String,
    val createdAt: LocalDateTime // 新增字段
)

@Configuration
class ApiVersionConfiguration : WebMvcConfigurer {
    
    override fun extendMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
        // 为不同 API 版本配置不同的转换策略
        val versionTolerantMapper = ObjectMapper().apply {
            // 忽略未知属性,保证向后兼容
            configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) 
            // 忽略空值属性
            setSerializationInclusion(JsonInclude.Include.NON_NULL) 
        }
        
        converters.add(0, MappingJackson2HttpMessageConverter(versionTolerantMapper))
    }
}

场景二:多格式数据支持

kotlin
@RestController
class DataController {
    
    @PostMapping("/data", consumes = ["application/json", "application/xml"])
    fun processData(@RequestBody data: ProcessRequest): ProcessResponse {
        // ✅ 同时支持 JSON 和 XML 格式的请求
        return dataService.process(data)
    }
    
    @GetMapping("/data/{id}", produces = ["application/json", "application/xml"])
    fun getData(@PathVariable id: Long): DataResponse {
        // ✅ 根据客户端 Accept 头返回对应格式
        return dataService.findById(id)
    }
}

Spring Boot 中的最佳实践

IMPORTANT

在 Spring Boot 应用中,推荐使用 HttpMessageConverters 机制或 extendMessageConverters 方法,而不是完全替换默认配置。

Spring Boot 自动配置增强

kotlin
@Configuration
class MessageConverterConfiguration {
    
    @Bean
    @Primary
    fun customObjectMapper(): ObjectMapper {
        return Jackson2ObjectMapperBuilder()
            .simpleDateFormat("yyyy-MM-dd HH:mm:ss")
            .serializers(LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
            .deserializers(LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
            .modules(
                ParameterNamesModule(), // Java 8 参数名支持
                Jdk8Module(), // Optional 等 Java 8 类型支持
                JavaTimeModule() // Java 8 时间类型支持
            )
            .build()
            .apply {
                // 自定义配置
                configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
            }
    }
}

常见问题与解决方案

问题 1:日期格式处理

WARNING

默认情况下,Jackson 会将日期序列化为时间戳,这可能不符合前端期望。

kotlin
// 解决方案:全局日期格式配置
@Configuration
class DateFormatConfiguration : WebMvcConfigurer {
    
    override fun extendMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
        converters.filterIsInstance<MappingJackson2HttpMessageConverter>()
            .forEach { converter ->
                converter.objectMapper.apply {
                    // 禁用时间戳格式
                    disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) 
                    // 设置日期格式
                    dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") 
                }
            }
    }
}

问题 2:循环引用处理

kotlin
data class Department(
    val id: Long,
    val name: String,
    @JsonManagedReference
    val employees: List<Employee>
)

data class Employee(
    val id: Long,
    val name: String,
    @JsonBackReference
    val department: Department
)

Jackson 模块生态系统

Spring Boot 会自动检测并注册以下常用模块:

模块功能使用场景
jackson-datatype-jsr310Java 8 日期时间 APILocalDateTime, LocalDate
jackson-datatype-jdk8Java 8 类型支持Optional, OptionalInt
jackson-module-kotlinKotlin 支持Kotlin 数据类、空安全等
jackson-module-parameter-names参数名支持构造函数参数绑定
完整的自定义配置示例
kotlin
@Configuration
class ComprehensiveMessageConverterConfig : WebMvcConfigurer {
    
    override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
        // 1. 配置 JSON 转换器
        val jsonBuilder = Jackson2ObjectMapperBuilder()
            .indentOutput(true)
            .dateFormat(SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
            .timeZone(TimeZone.getTimeZone("Asia/Shanghai"))
            .modulesToInstall(
                ParameterNamesModule(),
                Jdk8Module(),
                JavaTimeModule(),
                KotlinModule.Builder().build()
            )
            .featuresToDisable(
                SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
                DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
            )
            .featuresToEnable(
                JsonParser.Feature.ALLOW_COMMENTS,
                JsonParser.Feature.ALLOW_SINGLE_QUOTES
            )
        
        converters.add(MappingJackson2HttpMessageConverter(jsonBuilder.build()))
        
        // 2. 配置 XML 转换器
        val xmlMapper = jsonBuilder.createXmlMapper(true).build()
        converters.add(MappingJackson2XmlHttpMessageConverter(xmlMapper))
        
        // 3. 添加字符串转换器(处理纯文本)
        val stringConverter = StringHttpMessageConverter(StandardCharsets.UTF_8)
        stringConverter.supportedMediaTypes = listOf(
            MediaType.TEXT_PLAIN,
            MediaType.TEXT_HTML,
            MediaType.APPLICATION_JSON // 支持返回原始 JSON 字符串
        )
        converters.add(stringConverter)
    }
}

总结 🎉

HttpMessageConverter 是 Spring MVC 中的"翻译官",它让我们能够:

自动转换:无需手动处理 JSON/XML 与对象的转换
类型安全:强类型的 Kotlin 对象,编译时检查
格式灵活:支持多种数据格式(JSON、XML、自定义格式)
配置简单:通过配置类轻松自定义转换行为

TIP

在实际项目中,建议优先使用 extendMessageConverters() 方法来扩展默认配置,这样既能保持 Spring Boot 的自动配置优势,又能满足项目的特殊需求。

通过合理配置 Message Converters,你的 Spring Boot 应用将能够优雅地处理各种数据格式转换需求,为构建高质量的 RESTful API 奠定坚实基础! 🎯