SpringMVC的消息转换器

SpringMVC的消息转换器(Message Converter)是Spring框架中用于处理HTTP请求和响应体与Java对象之间转换的组件。它们使得开发人员可以轻松地将HTTP请求的数据映射到方法参数,并将返回的对象转换为HTTP响应。

工作原理

当一个HTTP请求到达Spring MVC应用程序时,框架会根据请求的内容类型(Content-Type)和接受类型(Accept)来选择合适的消息转换器。例如,如果客户端发送了一个JSON格式的POST请求,那么Spring MVC会选择MappingJackson2HttpMessageConverter来将请求体反序列化为Java对象。同样地,当方法返回一个Java对象并需要将其发送给客户端时,Spring MVC会使用相应的消息转换器来序列化这个对象。

常见的内置转换器

在SpringMVC中,消息转换器通常通过HttpMessageConverter接口实现。Spring MVC提供了多种内置的消息转换器来支持不同的媒体类型,例如JSON、XML等。以下是几个常见的内置转换器:

  • MappingJackson2HttpMessageConverter:用于支持JSON格式的HTTP消息,依赖于Jackson库。
  • MappingJackson2XmlHttpMessageConverter:用于支持XML格式的HTTP消息,同样依赖于Jackson库。
  • StringHttpMessageConverter:用于处理纯文本字符串的HTTP消息。
  • FormHttpMessageConverter:用于处理表单数据(application/x-www-form-urlencoded或multipart/form-data),包括标准表单和文件上传。
  • ByteArrayHttpMessageConverter:用于处理二进制数据,比如图片或文件下载。
  • Jaxb2RootElementHttpMessageConverter:基于JAXB API,用于XML数据的序列化和反序列化。
  • SourceHttpMessageConverter:用于处理基于javax.xml.transform.Source的XML消息。
  • ResourceHttpMessageConverter:用于处理资源文件,如文件下载

配置消息转换器

你可以通过以下几种方式配置消息转换器:

  1. 通过Java配置类

    如果你正在使用基于Java的配置,可以通过实现WebMvcConfigurer接口并重写configureMessageConverters方法来添加自定义的消息转换器:

    java 复制代码
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            // 添加默认的消息转换器
            WebMvcConfigurer.super.configureMessageConverters(converters);
            // 添加自定义的消息转换器
            converters.add(new CustomHttpMessageConverter());
        }
    }
  2. 通过Spring XML配置

    如果你还在使用XML配置,则可以在配置文件中定义<mvc:message-converters>元素:

    XML 复制代码
    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
            <!-- 其他自定义的消息转换器 -->
        </mvc:message-converters>
    </mvc:annotation-driven>
  3. 自动配置(Spring Boot)

    在Spring Boot中,框架会根据类路径上的库自动配置合适的消息转换器。例如,如果Jackson库在类路径上,Spring Boot会自动注册MappingJackson2HttpMessageConverter

自定义消息转换器

为了创建自定义的消息转换器,你需要实现HttpMessageConverter<T>接口,其中T是你想要转换的对象类型。这个接口有三个主要的方法需要实现:

  • boolean canRead(Class<?> clazz, MediaType mediaType):判断是否能够读取指定类型的对象。
  • boolean canWrite(Class<?> clazz, MediaType mediaType):判断是否能够写入指定类型的对象。
  • List<MediaType> getSupportedMediaTypes():返回支持的媒体类型列表。
  • T read(Class<? extends T> clazz, HttpInputMessage inputMessage):从HTTP输入消息中读取并转换为对象。
  • void write(T t, MediaType contentType, HttpOutputMessage outputMessage):将对象转换为HTTP输出消息。

一旦实现了上述接口,就可以将其添加到SpringMVC的配置中,以便框架知道在处理特定类型的HTTP消息时使用哪个转换器。除了实现HttpMessageConverter<T>接口外,你还可以通过配置类或XML文件控制哪些转换器被使用,以及它们的优先级顺序。这对于确保正确处理不同类型的消息非常重要。

自定义消息转换器实例

1. 创建业务对象

首先,我们需要一个Java类来表示我们要序列化和反序列化的数据模型。这里我们创建一个简单的CustomObject

java 复制代码
public class CustomObject {
    private Long id;
    private String name;

    // Getters and Setters
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "CustomObject{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

2. 实现自定义消息转换器

接下来,我们将创建一个自定义的消息转换器,用于处理特定媒体类型的数据。假设我们的自定义媒体类型是application/vnd.example.v1+json

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
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.util.Collections;

public class CustomJsonHttpMessageConverter extends AbstractHttpMessageConverter<CustomObject> {

    private final ObjectMapper objectMapper = new ObjectMapper();

    public CustomJsonHttpMessageConverter() {
        // 支持的媒体类型
        super(new MediaType("application", "vnd.example.v1+json"));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        // 指定转换器支持的类型
        return CustomObject.class.isAssignableFrom(clazz);
    }

    @Override
    protected CustomObject readInternal(Class<? extends CustomObject> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        // 读取并反序列化为CustomObject
        return objectMapper.readValue(inputMessage.getBody(), clazz);
    }

    @Override
    protected void writeInternal(CustomObject object, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        // 序列化CustomObject到输出流中
        objectMapper.writeValue(outputMessage.getBody(), object);
    }
}

3. 配置消息转换器

为了使Spring MVC知道使用我们自定义的消息转换器,我们需要在配置类中注册它。下面是如何在基于Java的配置中做到这一点:

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 添加自定义的消息转换器
        converters.add(new CustomJsonHttpMessageConverter());
        // 如果需要保留默认的消息转换器,可以这样添加
        // WebMvcConfigurer.super.configureMessageConverters(converters);
    }
}

4. 控制器层

现在,我们可以创建一个控制器来使用这个自定义的消息转换器:

java 复制代码
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/custom")
public class CustomController {

    @PostMapping(consumes = "application/vnd.example.v1+json", produces = "application/vnd.example.v1+json")
    public CustomObject createCustomObject(@RequestBody CustomObject customObject) {
        // 处理业务逻辑...
        System.out.println("Received: " + customObject);
        return customObject; // 返回相同的对象作为响应
    }

    @GetMapping(value = "/{id}", produces = "application/vnd.example.v1+json")
    public CustomObject getCustomObject(@PathVariable Long id) {
        // 模拟查询操作
        CustomObject obj = new CustomObject();
        obj.setId(id);
        obj.setName("Example Object " + id);
        return obj;
    }
}

5. 异常处理

当消息转换过程中发生错误时,Spring MVC会抛出HttpMessageNotReadableExceptionHttpMessageNotWritableException。你可以通过全局异常处理器来捕获这些异常并返回友好的错误信息给客户端。

java 复制代码
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<String> handleHttpMessageNotReadable(HttpMessageNotReadableException ex) {
        return new ResponseEntity<>("Error reading message: " + ex.getMessage(), HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(HttpMessageNotWritableException.class)
    public ResponseEntity<String> handleHttpMessageNotWritable(HttpMessageNotWritableException ex) {
        return new ResponseEntity<>("Error writing message: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

6. 全局配置与性能优化

如果你有多个自定义转换器或者想要对所有转换器进行一些全局配置(比如设置日期格式),你可以在配置类中做进一步的调整。此外,对于高流量的应用程序,考虑使用缓存或异步处理以提高性能。

例如,你可以配置Jackson的ObjectMapper以更好地控制JSON的序列化和反序列化行为:

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule()); // 支持Java 8时间API
        // 这里还可以设置其他全局配置选项
        return mapper;
    }
}

然后,在你的自定义消息转换器中注入这个ObjectMapper bean:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;

public class CustomJsonHttpMessageConverter extends AbstractHttpMessageConverter<CustomObject> {

    private final ObjectMapper objectMapper;

    @Autowired
    public CustomJsonHttpMessageConverter(ObjectMapper objectMapper) {
        super(new MediaType("application", "vnd.example.v1+json"));
        this.objectMapper = objectMapper;
    }

    // ... 省略其他方法 ...
}

这将确保所有的CustomJsonHttpMessageConverter实例都使用同一个配置过的ObjectMapper,从而简化了配置并且提高了代码的一致性。

7. 配置与自定义

使用@ConfigurationProperties进行配置

对于复杂的配置需求,可以使用@ConfigurationProperties来创建一个配置类,从而将配置属性外部化。例如,如果你想要为Jackson ObjectMapper配置多个属性,你可以这样做:

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JacksonConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.jackson")
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }
}

application.propertiesapplication.yml中添加相应的配置项:

javascript 复制代码
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=UTC
# 更多配置...
自定义序列化器和反序列化器

有时默认的行为可能不符合业务需求,这时可以通过实现自定义的序列化器和反序列化器来调整数据处理方式。以日期格式为例,如果想要统一处理Java 8的时间类型,可以注册JavaTimeModule模块:

java 复制代码
@Bean
public Module javaTimeModule() {
    return new JavaTimeModule();
}

还可以通过继承JsonSerializerJsonDeserializer来创建更加定制化的逻辑。

8. 性能优化

缓存ObjectMapper

确保ObjectMapper实例是单例且线程安全的,因为它不是线程安全的。通常情况下,Spring会为你管理这一点,但如果你手动创建了ObjectMapper,则需要特别注意。

异步处理

为了提高响应速度,尤其是在处理大文件或复杂对象时,可以考虑使用异步的消息转换。Spring MVC支持异步请求处理,允许你在后台线程池中执行耗时任务而不阻塞主线程。

java 复制代码
@GetMapping("/async/{id}")
public CompletableFuture<CustomObject> getCustomObjectAsync(@PathVariable Long id) {
    return CompletableFuture.supplyAsync(() -> {
        // 模拟耗时操作
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        CustomObject obj = new CustomObject();
        obj.setId(id);
        obj.setName("Async Example Object " + id);
        return obj;
    });
}

9. 安全性考虑

内容协商(Content Negotiation)

确保你的应用程序正确地实现了内容协商机制,以防止攻击者利用不期望的内容类型发起攻击。可以通过设置白名单来限制允许的内容类型,并确保所有转换器都只处理经过验证的媒体类型。

java 复制代码
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    configurer.favorPathExtension(false)
              .favorParameter(true)
              .parameterName("mediaType")
              .ignoreAcceptHeader(true)
              .useRegisteredExtensionsOnly(true)
              .defaultContentType(MediaType.APPLICATION_JSON)
              .mediaType("json", MediaType.APPLICATION_JSON)
              .mediaType("xml", MediaType.APPLICATION_XML);
}
数据验证

始终对输入的数据进行验证,即使是在序列化和反序列化之后。这可以帮助防止诸如注入攻击等安全问题。

10. Spring Boot集成

在Spring Boot中,许多配置已经被简化并自动化。你只需要确保所需的库(如Jackson)存在于类路径上,框架就会自动配置合适的消息转换器。此外,Spring Boot还提供了额外的功能,比如自动发现和注册转换器。

javascript 复制代码
# application.yml
spring:
  jackson:
    serialization:
      write-dates-as-timestamps: false
    deserialization:
      fail-on-unknown-properties: false

11. 日志记录

启用详细的日志记录有助于调试和监控消息转换过程中的任何问题。可以在application.properties中配置日志级别:

javascript 复制代码
logging.level.org.springframework.web=DEBUG
logging.level.com.fasterxml.jackson=DEBUG

12. 测试

编写单元测试和集成测试来验证消息转换器的行为是否符合预期非常重要。JUnit和MockMvc可以帮助你模拟HTTP请求并检查转换结果。

javascript 复制代码
@WebMvcTest
class CustomControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldReturnDefaultMessage() throws Exception {
        mockMvc.perform(get("/custom/1").accept(MediaType.parseMediaType("application/vnd.example.v1+json")))
              .andExpect(status().isOk())
              .andExpect(content().contentType("application/vnd.example.v1+json"))
              .andExpect(jsonPath("$.name").value("Example Object 1"));
    }
}

使用注解控制消息转换

在控制器层,你可以使用一些注解来控制消息转换的行为,比如@RequestBody@ResponseBody

  • @RequestBody:用于将HTTP请求体的内容映射到方法参数,并使用适当的HttpMessageConverter进行反序列化。
  • @ResponseBody:用于指示方法的返回值应该被直接写入HTTP响应体,而不是解析为视图。

此外,还有@RestController注解,它是一个组合注解,等价于同时使用@Controller@ResponseBody,简化了RESTful服务的开发。

相关推荐
咩咩啃树皮8 小时前
第40篇:Vue3组件化开发精讲——组件拆分、复用、父子通信、工程化架构
java·前端·架构
鱟鲥鳚8 小时前
Spring Boot 集成 LangChain4j:从模型调用到 Tool Calling(Demo版)
java·spring boot
大模型码小白10 小时前
【Python零基础教程】继承、多态与魔法函数:面向对象编程三大核心特性详解
java·大数据·开发语言·人工智能·python·ai编程
腾渊信息科技公司11 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
爱笑的源码基地12 小时前
高并发 Redis 缓存门诊HIS系统源码,含财务统计药房进销存
java·程序·门诊系统·诊所系统·云诊所源码
wuqingshun31415912 小时前
TCP超时重传机制是为了解决什么问题?
java
莫逸风14 小时前
【AgentScope 2.0】 0. 学习指南
java·llm·agent·agentscope
隔窗听雨眠14 小时前
Spring Boot在云原生时代的编程范式革新研究
spring boot·后端·云原生
z1234567898615 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
yaoxin52112315 小时前
470. Java 反射 - Member 接口与 AccessFlag
java·开发语言·python