![dda78517d1c4aa251260d43e8e44fa40.png](https://i-blog.csdnimg.cn/blog_migrate/3c95e39951a5fb2d99a0d7c7a02019f2.jpeg)
背景
- 前端和后端在时间格式的传递上都走的是时间戳(方便前端自由定制)
- 时间格式大多用的是LocalDate,并不是传统的Date
简单来说就是时间戳在各类形式下转日期类。
当有如下类似代码的时候,后端该做哪些配置来满足上述需求了?
@PostMapping("/date-test/post/{pathDate}") public LocalDateTime getDatePost(@RequestParam LocalDate date, @RequestParam LocalDateTime dateTime, @RequestParam Date originalDate, @PathVariable("pathDate") LocalDateTime datePath, LocalDate localDate, @RequestBody Person person, Person person2) { System.out.println(date); System.out.println(dateTime); System.out.println(originalDate); System.out.println(localDate); System.out.println(datePath); System.out.println(person); System.out.println(person2); return LocalDateTime.now(); }}
上述案例使用了post来接收参数,如果你是get请求,除了@RequestBody不能使用外,其他不受影响
请求的URL如下所示:
localhost:8099/date-test/post/1568796069368?date=1568796069368&localDate=1568796069368&dateTime=1568796069368&originalDate=1568796069368&name=codingOX&birth=1568796069368
其中json部分的配置如下
![072f831b7a54d91bd1d26be895dd40ca.png](https://i-blog.csdnimg.cn/blog_migrate/5d1a09647f4d9875189ff0cd7608051f.jpeg)
正文
SpringMVC处理参数,其核心接口是org.springframework.web.method.support.HandlerMethodArgumentResolver
相关的实现挺多的,如下所示,重点部分我都通过红色框框进行了标记。
![c71cfd7b3b9cb4bad6a3dc4ba8684620.png](https://i-blog.csdnimg.cn/blog_migrate/1de32234c19609a6f17b66758e3d3491.jpeg)
我们通过接口来分析下SpringMVC是如何处理参数的
public interface HandlerMethodArgumentResolver {/** * 判定当前处理器适合处理那种类型的数据 */boolean supportsParameter(MethodParameter parameter);/** * 对符合预期的数据,进行具体的逻辑处理 */@NullableObject resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;}
更详细的代码你可以查看上图给出的实现,整体而言就是根据根据supportsParameter的结果Boolean来判定当前类是否可以解析这个参数,如果可以继续调用resolveArgument对参数进行解析。
后面我们需要利用该特点来自定义处理类,下面将通过如下几种情况进行处理。
- 通过@RequestParam和@PathVariable修饰LocalDate
- 通过@RequestBody修饰LocalDate
- 不对LocaDate添加任何修饰
- 不对包含LocalDate的对象添加任何修饰
@RequestParam和@PathVariable
这种情况对应的是上面例子形参中的:
public LocalDateTime getDatePost(@RequestParam LocalDate date, .... @PathVariable LocalDateTime datePath, ....) {...}
Spring的源码部分,通过文字分析和解读起来太累,这里直接给出结论,下同,有兴趣的可以结合结论,反过来阅读。。。
这2个类对应的处理器分别是:
- org.springframework.web.method.annotation.RequestParamMethodArgumentResolver
- org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver
如果有兴趣,可以跟下源码,你可以发现,处理这2类注解的解析主要用到的是:WebDataBinder,如果要扩展这类参数的处理方式,可以使用org.springframework.core.convert.converter.Converter或者org.springframework.format.Formatter
这2者区别在于:
- Converter可以定义输入和输出
- Formatter默认输入是字符串,输出为自定义
你可以根据自身需求进行定制,这里我通过Convert转换LocalDate为例,进行转换:
/** * 接收 形参加有注解 @RequestParam和@PathVariable时候 字符串转Date相关类 * * @author Liu Chunfu * @date 2019-09-18 3:57 下午 **/public class CustomDateConverter { public static class LocalDateConvert implements Converter { @Override public LocalDate convert(String timestamp) { return LocalDateUtil.timestampToLocalDateTime(timestamp).toLocalDate(); } } ...}
定义后,还需要使用,这个使用也很简单,将其注入Spring的容器就可以了。
- 如果是Spring MVC,通过XML定义为类
- 如果是Spring Boot,在@Configuration下通过@Bean声明就可以了。
还是举个例子吧:
@Configurationpublic class CustomDateConfig{ @Bean public Converter localDateConverter() { //此处不能替换为lambda表达式 return new CustomDateConverter.LocalDateConvert(); }}
通过@RequestBody修饰
通过@RequestBody很明显的就是要通过JSON序列化进行处理,Spring默认的是Jackson进行处理,所以我们需要对默认的JSON处理器进行日期类型的添加(假如默认不带对应序列化器的情况下)
此时需要用的是JsonDeserializer这个类,对应的实现如下所示:
public class CustomDateDeserializer { /** * 反序列化LocalDateTime */ public static class LocalDateDeserializer extends JsonDeserializer { @Override public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { Long timestamp = Long.valueOf(p.getText()); Instant instant = Instant.ofEpochMilli(timestamp); LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.of("+8")); LocalDate localDate = localDateTime.toLocalDate(); return localDate; } } ....}
有了这个了类,还需要添加到Spring中,Spring利用的是ObjectMapper,我们自定义一个ObjectMapper替换原本的就可以了。
@Configurationpublic class CustomDateConfig { // 省略部分代码 /** * Json序列化和反序列化转换器,用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json */ @Bean public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); //不显示为null的字段 objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); // 忽略不能转移的字符 objectMapper.configure(JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER, true); // 过滤对象的null属性. objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); //忽略transient objectMapper.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true); objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); //LocalDateTime系列序列化和反序列化模块,继承自jsr310,我们在这里修改了日期格式 JavaTimeModule javaTimeModule = new JavaTimeModule(); // LocalDateTime javaTimeModule.addSerializer(LocalDateTime.class, new CustomDateSerializer.LocalDateTimeSerializer()); javaTimeModule.addDeserializer(LocalDateTime.class,new CustomDateDeserializer.LocalDateTimeDeserializer()); // LocalDate javaTimeModule.addSerializer(LocalDate.class, new CustomDateSerializer.LocalDateSerializer()); javaTimeModule.addDeserializer(LocalDate.class, new CustomDateDeserializer.LocalDateDeserializer()); //Date序列化和反序列化 javaTimeModule.addSerializer(Date.class,new CustomDateSerializer.DateSerializer()); javaTimeModule.addDeserializer(Date.class,new CustomDateDeserializer.DateDeserializer()); objectMapper.registerModule(javaTimeModule); return objectMapper; } // 省略部分代码}
外部参数什么注解也不加
这种情况对应的是形参中的:
public LocalDateTime getDatePost(...,LocalDate localDate,....) { .... }
这种情况下,通过Spring默认的ModelAttributeMethodProcessor进行处理,其处理逻辑为:通过反射创建一个对象,然会在反射对每个参数通过Convert进行转换赋值。
默认情况下,此种情况是会报错的,因为LocalDate没有构造函数。所以我们需要在默认调用之前,自定义处理这此类形参,此时利用的就是HandlerMethodArgumentResolver这个接口。
HandlerMethodArgumentResolver会在ModelAttributeMethodProcessor之前调用,该接口的声明非常简单。
todo
我们可以对此类参数进行自定义。
public class CustomDateArgumentResolverHandler { public static class LocalDateArgumentResolverHandler implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().equals(LocalDate.class); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { String timestamp = webRequest.getParameter(parameter.getParameterName()); LocalDateTime localDateTime = LocalDateUtil.timestampToLocalDateTime(timestamp); return localDateTime.toLocalDate(); } } ......}
在使用该类的时候,需要在配置类@Configuration中通过WebMvcConfigurer这个接口下的方法addArgumentResolvers来添加进去:
public void addArgumentResolvers(List resolvers)
大体的代码如下:
@Configurationpublic class CustomDateConfig implements WebMvcConfigurer { @Override public void addArgumentResolvers(List resolvers) { resolvers.add(new CustomDateArgumentResolverHandler.LocalDateArgumentResolverHandler()); // ..... }}
内部参数什么注解也不加
对应的情况是:
public LocalDateTime getDatePost(Person person2)
此时的处理情况符合上述提到的ModelAttributeMethodProcessor和Convert处理。
所以如果我们按照上面提到的方法进行了处理,那么不用担心时间类型无法解析的问题。
结语
关于时间LocalDate这个类型的处理都完成了,上面的代码还是比较凌乱的,改天我单独做一个Demo,放在GitHub上,不过我还是希望你手动自己试试!
如果本文对你有帮助,欢迎评论、点赞、转发、收藏、关注!