Springboot控制层不同类型参数接收

前言

和前端对接接口时,针对不同的请求方法和请求contentType,我们后端要编写不同的接收参数的控制层方法。所以大概的汇总下对接过程中常用的方法接参形式。

基本类型的接参

主要针对的是Get请求携带的参数获取。

@RequestParam获取请求参数值

如请求 http://localhost:8080/hello?param=100 ,使用@RequestParam注解声明接参数的名称,对于请求参数param对应的值自动封装到进方法参数param上

less 复制代码
@GetMapping("/hello")
public String hello(@RequestParam("param") String param) {
    return param;
}

@PathVariable获取路径变量的值

如请求 http://localhost:8080/{name}/hello, 使用 @PathVariable 注解声明路径上的参数名称,对应请求路径上的name值会封装进方法参数name上

less 复制代码
@GetMapping("/{name}/hello")
public String testHello(@PathVariable("name") String name) {
    return name;
}

复杂类型json的接参

针对Post请求,contentType对应的是application/json或者application/xml等类型

@RequestBody来获取body中的数据

使用@RequestBody注解将请求体中的数据绑定到一个 Java 对象 。User对象中包含id和name的参数,将从body请求数据中获取到id的值和name的值。

less 复制代码
@PostMapping("/bodyTest")
public User bodyTest(@RequestBody User user) {
    return user;
}

构造对应的测试Post请求地址:http://localhost:8080/bodyTest , contentType为application/json ,body的内容为 {"id":1,"name":2}

获取文件上传信息

可以通过使用MultipartFile类型的参数或者@RequestPart注解,可以获取文件内容,名称等信息。

单文件上传

请求路径 http://localhost:8080/testUploadFile, contentType为multipart/form-data , 参数为file,类型为文件格式。

typescript 复制代码
// 直接使用MultipartFile类型指定,名称为file
@PostMapping("/testUploadFile")
public String testUploadFile(MultipartFile file) {
    return file.getOriginalFileName();
}

// 使用RequestPart注解标记,名称为file
@PostMapping("/testUploadFile1")
public String testUploadFile1(@RequestPart("file") MultipartFile file) {
    return file.getOriginalFileName();
}

RequestPart接收参数名为file的文件,定义MultipartFile 为文件类型

多文件上传

如果要是遇到了多文件上传的场景,其实和上述单文件上传方法一致,只不过增加文件类型增加为数组格式 MultipartFile[]

less 复制代码
// 此时接收参数file名称的参数为MultipartFile数组的格式
@PostMapping("/testUploadFile2")
public String testUploadFile2(@RequestPart("file") MultipartFile[] file) {
    return Arrays.stream(file).map(MultipartFile::getOriginalFilename)
                    .collect(Collectors.joining(","));
}

RequestPart接收参数名为file的文件数组 。

文件上传+参数传递

如果要是遇到了文件上传的场景,同时需要接收文件参数。这个时候需要怎么办呢?当然还是有办法的,就是通过 @ModelAttribute 配合 @RequestPart 组合接收

less 复制代码
// user对象的属性直接写在form-data结构里的参数
@PostMapping("/testUploadFile3")
public String testUploadFile3(@ModelAttribute User user , 
                              @RequestPart("file") MultipartFile[] file) {
    String collect = Arrays.stream(file).map(MultipartFile::getOriginalFilename).collect(Collectors.joining(","));
    return user.getName() + " " + collect;
}

user对象属性接收表单内其他属性,RequestPart接收参数名为file的文件类型

针对Enum枚举类型的简单配置

如果使用的枚举对象,它被定义的时候不包含其他属性,此时能被自动序列化和反序列化,无需额外的配置

对于有属性的枚举值

此时针对如下的枚举值在序列化和反序列化时就会遇到问题。

arduino 复制代码
public enum Status {

    SUCCESS("成功",1),FAIL("失败",1)

    ;

    private String name;
    private int code;
    private Status(String name,int code){
        this.name = name;
        this.code = code;
    }

    // 反序列化和序列化方法的关键(指定了using-to-string)
    @Override
    public String toString() {
        return this.name;
    }
}

此时可以通过配置文件定义枚举的序列化和反序列化的方式,在将包含枚举类型的 Java 对象序列化为 JSON 时,会调用枚举类型的toString方法来获取要序列化的值

vbnet 复制代码
spring:
  jackson:
    serialization:
      write-enums-using-to-string: true
    deserialization:
      read-enums-using-to-string: true

针对数据的null的忽略

遇到有些参数存在null的场景,可以通过配置输出给前端的时候默认就忽略掉带有null值的字段。

方法一: 通过全局的配置

java 复制代码
spring:
  jackson:
    default-property-inclusion: non_null

方法二: 通过实体类属性上的注解

java 复制代码
@JsonInclude(Include.NON_NULL)
private String name;

针对Date类型的接发参的简单配置

问题产生的场景

  • 场景一: 通过Get请求,入参是Date类型
  • 场景二: 通过Post场景,入参是实体类,实体类包含Date类型参数

结论: 以上情况使用时间戳和字符串都无法正确赋值到参数上。那么如何才能使用参数正确的被反序列化上值呢?可以简单的通过springboot内置的注解

配置统一格式返回给前端

当我们返回给前端字段属性为Date类型,要保证前端接收到的Date类型数据统一为一种时间格式类型,可以通过配置文件直接配置,或者通过注解单独生效。

方法一:生效全局的配置 application.yml

yaml 复制代码
spring:
  jackson:
    // 第一种:使用字符串时间格式接收和输出
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    // 第二种:使用时间戳格式接收和输出
    serialization:
      write-dates-as-timestamps: true
    deserialization:
      read-date-timestamps-as-nanoseconds: true

方法二: 单独实体类的属性上的配置

使用@JsonFormat 或者 @DateTimeFormat 注解

java 复制代码
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") // 第一种:使用字符串时间格式接收输出
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") // 第二种:使用字符串时间格式接受输出
private Date created;

注意: 和前端同学约定好统一一种就行,字符串或者时间戳。

自定义实现序列化和反序列化的规则类

反序列化对LocalDateTime的配置实现,然后通过deserialize方法,获取到前端传递的jsonParser.getText()文本内容,然后再去通过工具类转换成LocalDateTime时间内容

scala 复制代码
// 对该类型的反序列化的转化类
public class CustomLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {

    @Override
    public LocalDateTime deserialize(JsonParser jsonParser, 
DeserializationContext deserializationContext) throws IOException, IOException {
        if (StringUtils.isEmpty(jsonParser.getText())) {
            return null;
        }
        return Instant
                .ofEpochMilli(Long.parseLong(jsonParser.getText()))
                .atZone(ZoneOffset.ofHours(8))
                .toLocalDateTime();
    }
}

序列化对LocalDateTime的配置实现 , 然后通过LocalDateTime的值通过工具类转换成对应前端想要的值,然后通过 jsonGenerator来write输出给前端。

scala 复制代码
public class CustomLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {


    @Override
    public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator,
                          SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeNumber(localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli());
    }


}

使用消息转换器的配置

Springboot 支持个性化定制处理消息转换配置的接口,开发同学可以根据自己的需求来实现定制。比如针对一些LocalDateTime,LocalDate等时间类型使用自定义代码规则处理。

如下两种方式:

  • 对已经存在的对jackson处理的消息转换器修改
  • 也可以通过配置convert中添加进自己的定义的转换器

第一种直接修改ObjectMapper的Bean

修改 MappingJackson2HttpMessageConverterObjectMapper, 通过自定义这个对象来实现对指定类型的序列化和反序列化的结果。

arduino 复制代码
@Bean
public ObjectMapper objectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();
    SimpleModule module = new SimpleModule();
    // 注册自定义反序列化器和序列化器
    module.addDeserializer(LocalDateTime.class, new CustomDeserializer());
    module.addSerializer(LocalDateTime.class, new CustomSerializer())
    objectMapper.registerModule(module);
    return objectMapper;
}

第二种自定义MappingJackson2HttpMessageConverter添加

使用 WebMvcConfigurer 提供的 configureMessageConverters 方法 , 用于自定义消息转换器(Message Converters)的配置,负责将请求体中的数据转换为 Java 对象(在请求处理阶段),以及将 Java 对象转换为响应体(在响应发送阶段)。

常用的消息转换器

  • FastJsonHttpMessageConverter : 阿里开发,可以替代springboot默认的jackson的对json序列化的操作
    • 使用场景: application/json媒体类型的请求和响应
  • StringHttpMessageConverter :处理字符串类型的消息转换
    • 使用场景: text/plain媒体类型的请求和响应
  • ByteArrayHttpMessageConverter : 处理字节数组和http信息的转换
    • 使用场景: 支持多种媒体类型,包括application/octet - stream(用于通用的二进制数据)等
  • MappingJackson2HttpMessageConverter: Springboot依赖的核心库jackson对json序列化的处理
    • 使用场景: application/json媒体类型的请求和响应

在converts消息转换器列表中添加自己的定义的消息转换器,添加到顶部,对于处理上application/json 的时候会走我们配置的消息转换器上

java 复制代码
@Configuration
public class WebCustomConfiguration implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        // 注册自定义反序列化器和序列化器
        module.addDeserializer(LocalDateTime.class, new CustomDeserializer());
        module.addSerializer(LocalDateTime.class, new CustomSerializer())
        objectMapper.registerModule(module);
        MappingJackson2HttpMessageConverter jacksonConvert = new MappingJackson2HttpMessageConverter(objectMapper);
        // 添加到converts消息转换器列表中的顶部
        converters.add(0, jacksonConvert);
    }
}

Jackson替换为FastJson消息转换器

注意: 前面提到的常用消息转换器 FastJsonHttpMessageConverter ,也是可以针对application/json数据参数类型进行处理。那我们需要如何用 FastJsonHttpMessageConverter 去替换来解析生效呢?

增加fastjson的依赖

xml 复制代码
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>版本号</version>
    </dependency>

编写 FastJsonHttpMessageConverter

scss 复制代码
public FastJsonHttpMessageConverter fastJsonHttpMessageConverter() {
    // 创建FastJsonConfig对象
    FastJsonConfig fastJsonConfig = new FastJsonConfig();
    // 设置自定义序列化类(!!)
    fastJsonConfig.setSerializer(CustomUserSerializer.class);
    // 创建FastJsonHttpMessageConverter对象
    FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
    // 设置配置信息
    converter.setFastJsonConfig(fastJsonConfig);
    // 设置支持的媒体类型
    List<MediaType> supportedMediaTypes = new ArrayList<>();
    supportedMediaTypes.add(MediaType.APPLICATION_JSON);
    converter.setSupportedMediaTypes(supportedMediaTypes);
    return converter;
}

然后再添加到我们的 List<HttpMessageConverter<?>> converts集合中。同时需要注意下生效顺序。

直接添加序列化和反序列化器

除了前面提到的直接修改ObjectMapper的bean , springboot提供了通过实现Jackson2ObjectMapperBuilderCustomizer接口,可以对ObjectMapper进行高度定制化的配置。

csharp 复制代码
/**
  * Jackson序列化和反序列化转换器,用于转换Post请求体中的json以及将对象序列化为返回响应的json
  */
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
    return builder -> builder
            .serializerByType(LocalDateTime.class, new CustomSerializer())
            .deserializerByType(LocalDateTime.class,  new CustomDeserializer())
            ;
}

总得来说实现逻辑还是将自定义的序列化和反序列化的规则的类加进配置中,对不同类型的处理,应用到ObjectMapper,从而影响json的序列化和返序列化。

通过自定义注解来实现序列化和反序列化

通过注解上添加@JacksonAnnotationsInside和序列化的对应的规则类(前面定义过的)。

less 复制代码
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonDeserialize(using = CustomDeserializer.class)
public @interface StampToLocalDateTime {

}

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = CustomSerializer.class)
public @interface LocalDateTimeToStamp {

}

然后可以对于你想使用对应字段应用的序列化和反序列化的地方,增加上述的注解使用即可。

java 复制代码
@Data
public class TestDate {
    @StampToLocalDateTime
    @LocalDateTimeToStamp
    private LocalDateTime date;
}

总结

  • 对于一般的Get请求
    • 使用@RequesParam 注解生效字段绑定属性
    • 使用@PathVariable 注解生效获取路径上属性
  • 对于一般的post请求
    • application/json ,使用@RequestBody注解反序列化到对象属性上
    • multipart/form-data ,使用@RequestPart 获取文件信息
  • 对于date类型/enum类型,null的忽略的统一序列化和反序列化值
    • 通过框架给的注解实现
    • 通过全局配置文件配置生效
  • 对于自定义参数序列化和反序列化
    • 通过ObjectMapper的绑定针对具体参数的序列化/反序列化方法
    • 通过springboot给定的接口实现Jackson2ObjectMapperBuilderCustomizer
    • 通过自定义注解封装实现

最后,还是和前端约定好对应类型的统一的出入参数,约定好不同请求对应的不同的相关的的请求方式和类型。

相关推荐
14L4 小时前
互联网大厂Java面试:从Spring Cloud到Kafka的技术考察
spring boot·redis·spring cloud·kafka·jwt·oauth2·java面试
地藏Kelvin5 小时前
Spring Ai 从Demo到搭建套壳项目(二)实现deepseek+MCP client让高德生成昆明游玩4天攻略
人工智能·spring boot·后端
一个有女朋友的程序员5 小时前
Spring Boot 缓存注解详解:@Cacheable、@CachePut、@CacheEvict(超详细实战版)
spring boot·redis·缓存
wh_xia_jun5 小时前
在 Spring Boot 中使用 JSP
java·前端·spring boot
yuren_xia6 小时前
在Spring Boot中集成Redis进行缓存
spring boot·redis·缓存
yuren_xia6 小时前
Spring Boot + MyBatis 集成支付宝支付流程
spring boot·tomcat·mybatis
我爱Jack8 小时前
Spring Boot统一功能处理深度解析
java·spring boot·后端
RainbowJie19 小时前
Spring Boot 使用 SLF4J 实现控制台输出与分类日志文件管理
spring boot·后端·单元测试
面朝大海,春不暖,花不开9 小时前
Spring Boot MVC自动配置与Web应用开发详解
前端·spring boot·mvc
发愤图强的羔羊9 小时前
SpringBoot异步导出文件
spring boot·后端