【Spring】详解Spring MVC中不同格式的POST请求参数的数据类型转换过程
2017-04-23 11:05
776 查看
你也许写过很多Controller,那你可曾和我一样好奇最初字符串格式的HTTP请求参数如何转化成类型各异的Controller方法参数?
引子:假设现在有一个Long型的请求参数,需要转化为OffsetDateTime类型的方法参数,请问如何实现?
对于不同类型的请求格式,Spring有着不同的转换过程(从请求参数到方法参数),请看下图。
从上图可以看到,Spring在解析请求参数时,会根据请求格式进入到不同的转换流程:
如果是非raw请求(即包含参数数组),则交由ModelAttributeMethodProcessor处理,ModelAttributeMethodProcessor再调用Spring Converter SPI对请求参数逐个进行转换。
如果是raw请求,则交由RequestResponseBodyMethodProcessor处理,对于JSON格式的请求体,会再调用MappingJackson2HttpMessageConverter,最终通过ObjectMapper完成转换。
*关于Spring Converter SPI的进一步解读,可参考这篇文章
回到开头的那个问题,答案就很简单了。如果是非raw请求,则需要实现一个自定义的Long->OffsetDatetime的Converter;如果是raw请求,则确保ObjectMapper中包含一个Long->OffsetDatetime的反序列化器,注册Jackon自带的JavaTimeModule即可。
1. 实现
2. 选择一个标注@Configuration注解的配置类,继承
1. 继承
2. 继承
3. 选择一个标注@Configuration注解的配置类,通过@Bean注解将自定义的Jackson Module注册为Bean,Spring Boot会自动发现和注册这个Module到默认的ObjectMapper中。
示例代码参见下一小节。
完整代码可以参见我在GitHub上的示例工程。
Spring Boot Reference Guide
SpringMVC数据类型转换
引子:假设现在有一个Long型的请求参数,需要转化为OffsetDateTime类型的方法参数,请问如何实现?
1 常见的POST请求格式
首先,让我们看一下3种常见的POST请求格式:application/x-www-form-urlencoded: 默认的表单提交格式,不支持文件
multipart/form-data: 用于上传文件,同时也支持普通类型的参数
application/json: 提交JSON格式的raw数据,适用于AJAX请求和REST风格的接口
对于不同类型的请求格式,Spring有着不同的转换过程(从请求参数到方法参数),请看下图。
2 Spring MVC中的数据类型转换过程
从上图可以看到,Spring在解析请求参数时,会根据请求格式进入到不同的转换流程:
如果是非raw请求(即包含参数数组),则交由ModelAttributeMethodProcessor处理,ModelAttributeMethodProcessor再调用Spring Converter SPI对请求参数逐个进行转换。
如果是raw请求,则交由RequestResponseBodyMethodProcessor处理,对于JSON格式的请求体,会再调用MappingJackson2HttpMessageConverter,最终通过ObjectMapper完成转换。
*关于Spring Converter SPI的进一步解读,可参考这篇文章
回到开头的那个问题,答案就很简单了。如果是非raw请求,则需要实现一个自定义的Long->OffsetDatetime的Converter;如果是raw请求,则确保ObjectMapper中包含一个Long->OffsetDatetime的反序列化器,注册Jackon自带的JavaTimeModule即可。
2.1 如何注册自定义Converter?
以Spring Boot为例,1. 实现
org.springframework.core.convert.converter.Converter接口生成一个自定义Converter。
public class OffsetDateTimeConverter implements Converter<String, OffsetDateTime> { @Override public OffsetDateTime convert(String source) { if (!NumberUtils.isNumber(source)) { return null; } Long milli = NumberUtils.createLong(source); return OffsetDateTime.ofInstant(Instant.ofEpochMilli(milli), systemDefault()); } }
2. 选择一个标注@Configuration注解的配置类,继承
org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter,然后覆盖addFormatters方法,注册自定义Converter。
@Configuration public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new OffsetDateTimeConverter()); } }
2.2 如何注册自定义Jackson Deserializer和Serializer?
以Spring Boot为例,1. 继承
com.fasterxml.jackson.databind.JsonDeserializer和
com.fasterxml.jackson.databind.JsonSerializer生成自定义Jackson Deserializer和Serializer。
2. 继承
com.fasterxml.jackson.databind.module.SimpleModule生成一个自定义Jackson Module,在其中添加自定义的Jackson Deserializer和Serializer。
3. 选择一个标注@Configuration注解的配置类,通过@Bean注解将自定义的Jackson Module注册为Bean,Spring Boot会自动发现和注册这个Module到默认的ObjectMapper中。
示例代码参见下一小节。
3 更多示例
3.1 演示Controller
演示3种常见的GET, POST请求参数的数据类型转换。@org.springframework.web.bind.annotation.RestController @Validated public class RestController implements IController { private static final List<DayOfWeek> WEEKENDS = Lists.newArrayList(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY); /** * 转换GET请求参数 */ @RequestMapping(value = "/isWeekend", method = RequestMethod.GET) public JsonResult<Boolean> isWeekend(@Valid VacationRequest request) { return JsonResult.ok(WEEKENDS.contains(request.getStart().getDayOfWeek())); } /** * 转换POST请求体 */ @RequestMapping(value = "/approve", method = RequestMethod.POST) public VacationApproval vacate(@RequestBody @Valid VacationRequest request) { return VacationApproval.approve(request); } /** * 转换POST请求参数 */ @RequestMapping(value = "/deny", method = RequestMethod.POST) public VacationApproval deny(@Valid VacationRequest request) { return VacationApproval.deny(request); } }
3.2 自定义Enum Converter(用于非raw格式的请求)
基于特定属性的枚举数据类型转换器,如果无法找到,再尝试用枚举名进行转换。public static class CustomEnumConverter<T extends Enum<T>> implements Converter<String, T> { private Class<T> enumCls; private String prop; /** * @param enumCls 枚举类型 * @param prop 属性名 */ public CustomEnumConverter(Class<T> enumCls, String prop) { this.enumCls = enumCls; this.prop = prop; } @Override public T convert(String source) { if (StringUtils.isEmpty(source)) { return null; } return Enums.getEnum(enumCls, prop, source).orElseGet(() -> Stream.of(enumCls.getEnumConstants()) .filter(e -> e.name().equals(source)) .findFirst().orElse(null) ); } }
3.3 自定义Module(用于raw格式的请求)
用于注册自定义Enum Serializer和Enum Deserializer。public class CustomEnumModule extends SimpleModule { /** * @param prop 属性名 */ public CustomEnumModule(@NotNull String prop){ Asserts.notBlank(prop); addDeserializer(Enum.class, new CustomEnumDeserializer(prop)); addSerializer(Enum.class, new CustomEnumSerializer(prop)); } }
3.3.1 自定义Enum Serializer
自定义枚举序列化器,查找特定属性并进行序列化,如果无法找到,则序列化为枚举名。@Slf4j public class CustomEnumSerializer extends JsonSerializer<Enum> { private String prop; /** * @param prop 属性名 */ public CustomEnumSerializer(@NotNull String prop) { Asserts.notBlank(prop); this.prop = prop; } @Override public void serialize(Enum value, JsonGenerator gen, SerializerProvider serializers) throws IOException { if (value == null) { gen.writeNull(); return; } try { PropertyDescriptor pd = getPropertyDescriptor(value, prop); if (pd == null || pd.getReadMethod() == null) { gen.writeString(value.name()); return; } Method m = pd.getReadMethod(); m.setAccessible(true); gen.writeObject(m.invoke(value)); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { throw new CommonException(e); } } }
3.3.2 自定义Enum Deserializer
自定义枚举反序列化器,根据特定属性进行反序列化,如果无法找到,再尝试用枚举名进行反序列化。public class CustomEnumDeserializer extends JsonDeserializer<Enum> implements ContextualDeserializer { @Setter private Class<Enum> enumCls; private String prop; /** * @param prop 属性名 */ public CustomEnumDeserializer(@NotNull String prop) { Asserts.notBlank(prop); this.prop = prop; } @Override public Enum deserialize(JsonParser parser, DeserializationContext ctx) throws IOException { String text = parser.getText(); return Enums.getEnum(enumCls, prop, text).orElseGet(() -> Stream.of(enumCls.getEnumConstants()) .filter(e -> e.name().equals(text)) .findFirst().orElse(null) ); } @Override public JsonDeserializer createContextual(DeserializationContext ctx, BeanProperty property) throws JsonMappingException { Class rawCls = ctx.getContextualType().getRawClass(); Asserts.isTrue(rawCls.isEnum()); Class<Enum> enumCls = (Class<Enum>) rawCls; CustomEnumDeserializer clone = new CustomEnumDeserializer(prop); clone.setEnumCls(enumCls); return clone; } }
完整代码可以参见我在GitHub上的示例工程。
4 参考
四种常见的 POST 提交数据方式Spring Boot Reference Guide
SpringMVC数据类型转换
相关文章推荐
- Spring MVC详解(七) 注解式控制器的数据验证、类型转换及格式化(2)
- spring mvc 访问不同的目录下的jsp视图及处理POST请求参数中文乱码问题
- Spring mvc 针对get 和 post 请求参数的不同接收方式
- 详解 Java中日期数据类型的处理之格式转换的实例
- Spring MVC详解(七) 注解式控制器的数据验证、类型转换及格式化(1)
- Spring MVC详解(七) 注解式控制器的数据验证、类型转换及格式化(1)
- Spring(四):spring mvc模型数据传递、请求参数处理
- JAVAWEB开发之Struts2详解(二)——Action接受请求参数、类型转换器、使用Struts2的输入校验、以及遵守约定规则实现Struts2的零配置
- java数据类型转换____request请求参数类型转换
- Action参数封装过程中,数据类型转换问题
- 解决post请求接收数据类型为text/html时参数传递的问题
- Spring MVC根据请求后缀返回不同数据格式
- Spring MVC详解(七) 注解式控制器的数据验证、类型转换及格式化(2)
- spring mvc接收参数方式,json格式返回请求数据
- Http Post请求提交json格式数据工具类,解决请求参数中文乱码问题
- 表单中多个请求参数名字相同,服务器到底获取的是哪个请求参数的值,表单的action和get和post提交方式的关系以及提交数据的不同点
- ajax请求当发送post方式application/json格式数据,url后面又带有参数的时候
- IOS中网络数据请求过程详解(GET POST方法使用)
- .net学习之.net和C#关系、运行过程、数据类型、类型转换、值类型和引用类型、数组以及方法参数等
- Spring的Controller请求方法中参数名匹配,但是参数类型不同会报404