前端和后端在时间格式的传递上都走的是时间戳
(方便前端自由定制)
时间格式大多用的是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
部分的配置如下
SpringMVC处理参数,其核心接口是org.springframework.web.method.support.HandlerMethodArgumentResolver
相关的实现挺多的,如下所示,重点部分我都通过红色框框进行了标记。
我们通过接口
来分析下SpringMVC
是如何处理参数的
public interface HandlerMethodArgumentResolver {
/**
* 判定当前处理器适合处理那种类型的数据
*/
boolean supportsParameter(MethodParameter parameter);
/**
* 对符合预期的数据,进行具体的逻辑处理
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
更详细的代码你可以查看上图给出的实现,整体而言就是根据根据supportsParameter
的结果Boolean
来判定当前类是否可以解析这个参数,如果可以继续调用resolveArgument
对参数进行解析。
后面我们需要利用该特点来自定义处理类,下面将通过如下几种情况进行处理。
@RequestParam
和@PathVariable
修饰LocalDate
@RequestBody
修饰LocalDate
LocaDate
添加任何修饰LocalDate
的对象添加任何修饰这种情况对应的是上面例子形参中的:
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<String, LocalDate> {
@Override
public LocalDate convert(String timestamp) {
return LocalDateUtil.timestampToLocalDateTime(timestamp).toLocalDate();
}
}
...
}
定义后,还需要使用,这个使用也很简单,将其注入Spring
的容器就可以了。
Spring MVC
,通过XML
定义为类Spring Boot
,在@Configuration
下通过@Bean
声明就可以了。还是举个例子吧:
@Configuration
public class CustomDateConfig{
@Bean
public Converter<String, LocalDate> localDateConverter() {
//此处不能替换为lambda表达式
return new CustomDateConverter.LocalDateConvert();
}
}
@RequestBody
修饰通过@RequestBody
很明显的就是要通过JSON
序列化进行处理,Spring
默认的是Jackson
进行处理,所以我们需要对默认的JSON
处理器进行日期类型的添加(假如默认不带对应序列化器的情况下)
此时需要用的是JsonDeserializer
这个类,对应的实现如下所示:
public class CustomDateDeserializer {
/**
* 反序列化LocalDateTime
*/
public static class LocalDateDeserializer extends JsonDeserializer<LocalDate> {
@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
替换原本的就可以了。
@Configuration
public 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<HandlerMethodArgumentResolver> resolvers)
大体的代码如下:
@Configuration
public class CustomDateConfig implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new CustomDateArgumentResolverHandler.LocalDateArgumentResolverHandler());
// .....
}
}
对应的情况是:
public LocalDateTime getDatePost(Person person2)
此时的处理情况符合上述提到的ModelAttributeMethodProcessor
和Convert
处理。
所以如果我们按照上面提到的方法进行了处理,那么不用担心时间类型无法解析的问题。
关于时间LocalDate
这个类型的处理都完成了,上面的代码还是比较凌乱的,改天我单独做一个Demo
,放在GitHub
上,不过我还是希望你手动自己试试!