前言
和前端对接接口时,针对不同的请求方法和请求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
修改 MappingJackson2HttpMessageConverter
的ObjectMapper
, 通过自定义这个对象来实现对指定类型的序列化和反序列化的结果。
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
- 通过自定义注解封装实现
- 通过
最后,还是和前端约定好对应类型的统一的出入参数,约定好不同请求对应的不同的相关的的请求方式和类型。