先看如下Controller代码
@RequestMapping("/getDateParam")
@ResponseBody
public String getDateParam(@RequestParam Date date){
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
System.out.println(date);
return dateFormat.format(date);
}
@RequestMapping("/getDateObject")
@ResponseBody
public String getDateObject(DateParam dateParam){
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
System.out.println(dateParam.getDate());
return dateFormat.format(dateParam.getDate());
}
@RequestMapping("/getDateJSON")
@ResponseBody
public String getDateJSON(@RequestBody DateParam dateParam){
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
System.out.println(dateParam.getDate());
return dateFormat.format(dateParam.getDate());
}
DateParam类定义如下
import java.util.Date;
public class DateParam {
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
使用postman请求
getDateParam方法通过@RequestParam请求接收报错。SpringMVC没有默认的String转换为Date类型的默认设置。
getDateObject方法通过直接对象DateParam接受,也报错。SpringMVC没有默认的字符串转换为日期类型的转换器设置
通过@RequestBody接收不报错。Spring Boot的Json参数默认使用了jackson,Jackson有默认的日期格式处理。但该默认的格式也是有些问题的,比如如下请求:
如图,你可以看到我们加了时分秒之后也报错了。因为Jackson的默认日期处理格式中间有一个T字符,如我们的参数定义为
但我们得到的时间还是有8小时的差距。这个问题我们下面再解决。
使用@DateTimeFormat解决获取日期参数报错
直接实体类接受日期类型和通过@RequestParam注解获取的日期参数报错,可以使用@DateTimeFormat注解来解决问题
@RequestMapping("/getDateParam")
@ResponseBody
public String getDateParam(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date date){
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
System.out.println(date);
return dateFormat.format(date);
}
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
public class DateParam {
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
@NumberFormat和@DateTimeFormat详解
@NumberFormat:定义数字相关的解析/格式化元数据(通用样式、货币样式、百分数样式),参数如下:
style:用于指定样式类型,包括三种:Style.NUMBER(通用样式) Style.CURRENCY(货币样式) Style.PERCENT(百分数样式),默认Style.NUMBER;
pattern:自定义样式,如patter="#,###";
@DateTimeFormat:定义日期相关的解析/格式化元数据,参数如下:
pattern:指定解析/格式化字段数据的模式,如”yyyy-MM-dd HH:mm:ss”(我们示例中使用的此方式)
iso:指定解析/格式化字段数据的ISO模式,包括四种:ISO.NONE(不使用) ISO.DATE(yyyy-MM-dd) ISO.TIME(hh:mm:ss.SSSZ) ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ),默认ISO.NONE;
style:指定用于格式化的样式模式,默认“SS”,具体使用请参考Joda-Time类库的org.joda.time.format.DateTimeFormat的forStyle的javadoc;
优先级: pattern 大于 iso 大于 style。
使用@DateTimeFormat解决非常简单,但是需要我们在每一个需要格式化转换的字段上添加该注解。那么有没有全局设置的方式呢?
使用Converter接口设置默认的类型转换
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
@Configuration
public class Config implements WebMvcConfigurer {
@Resource
private ApplicationContext context;
@Override
public void addFormatters(FormatterRegistry registry) {
// context.getBeansOfType(Converter.class)不会为空,所以不用判断空指针
Collection<Converter> converters = context.getBeansOfType(Converter.class).values();
for(Converter converter : converters) {
registry.addConverter(converter);
}
}
@Bean
public Converter getConverter(){
// 注意Converter中的代码需要确保线程安全
Converter<String, Date> converter = new Converter<String,Date>(){
@Override
public Date convert(String source) {
if(source == null){
return null;
}
String formatStr = "yyyy-MM-dd";
DateFormat df = null;
if(formatStr.length() == source.length()){
df = new SimpleDateFormat(formatStr);
}else {
df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
try {
return df.parse(source);
} catch (ParseException e) {
return null; // 出现异常,直接返回null
}
}
};
return converter;
}
}
Spring MVC中的许多配置都需要实现WebMvcConfigurer接口来注册、添加我们的自定义配置。如这里的转换类,以及我们后面要添加的拦截器等等。
示例中我们的Converter使用了@Bean加入的容器,而且定义的方法内部类,此写法只是为了方便。实际应用中请将Converter单独定义,可以使用@Component注解加入spring容器即可。
WebMvcConfigurer的addFormatters方法负责添加我们的转换器配置、或者Formatter。通过FormatterRegistry的如下方法来添加
registry.addConverter(Converter<?, ?> converter);
registry.addConverter(GenericConverter converter);
registry.addConverterFactory(ConverterFactory<?, ?> factory);
Converter、GenericConverter 、ConverterFactory的区别
- 他们都是类型转换器
- Converter实现最简单
- ConverterFactory稍微复杂一点,但比Converter灵活一些
- GenericConverter实现最为复杂,但使用非常灵活,但比较复杂,非必要我们一般不使用,一般情况下我们使用Converter就行了。
Formatter接口
FormatterRegistry也有对应的方法来添加Formatter。这里我们不做深入的讨论了。
json格式的请求参数的日期格式处理
处理方式和使用的json处理框架有关系,Spring中默认使用的是Jackson,所以我们此处讨论的是Jackson的处理方式。
其实我们上面的示例中,json格式的日期有8小时的时间差的问题,我们在此一并解决了。
使用@JsonFormat
// pattern规定日期格式,timezone指定时区(示例指定的是北京时间所在时区)
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss" ,timezone = "GMT+8")
private Date date;
同@DateTimeFormat,@JsonFormat也需要在所有的对应的参数定义上添加注解。
String Boot环境下添加如下配置即可
# 设置json转换的日期格式
spring.jackson.date-format = yyyy-MM-dd HH:mm:ss
# 设置json转换的日期时区
spring.jackson.time-zone = GMT+8
有个问题在于,我们设置了格式为yyyy-MM-dd HH:mm:ss后,我们提交参数如果是2021-09-09,也会报错。这一点需要注意。可以要求前端提交的时候加上后面的时分秒来规避。特殊情况下可以使用@JsonFormat
直接配置MappingJackson2HttpMessageConverter
在配置文件中添加如下代码
@Bean
public MappingJackson2HttpMessageConverter getJackson2HttpConverter(){
ObjectMapper mapper = new ObjectMapper();
// 禁用位置属性验证,遇到未知属性时不拋异常
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
//-- 配置Jackson默认处理的日期类型格式(默认是日期的毫秒数)
//-- 如果需要其他格式如yyyy-MM-dd,请在相应Bean对应的属性上设置@JsonFormat(pattern = "yyyy-MM-dd")
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
mapper.setDateFormat(format);
mapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(mapper);
return converter;
}
此方式覆盖了spring boot提供的自动配置,在能满足要求的情况下,建议使用spring boot配置的方式来设置。
本章我们实际上只讨论了日期格式的字符串转换为对应的后端参数的方式,实际上SpringMVC的类型转换还更强大。但就目前而言,大部分的项目都可以直接使用了,没必要搞得那么复杂。