springboot-mvc:入参日期类型转换String->Date
4种方式:
1.通过在application.ym中配置 spring.mvc.data-format: yyyy-MM-dd HH:mm:ss ,使用的是ParserConverter
- 优点:简单的配置就可以,很方便
- 缺点:只能设置一种格式生效(ps:可以通过addFormatterForFieldType方法设置多种格式,但是它内部维护的是一个linkedList,会将最后设置的addFirst,查找时从头开始找,找到即返回,所以生效的始终是最后设置的那个格式),参考如下配置,当传入yyyy/MM/dd这种格式时会报错
第一步:
spring:
mvc:
date-format: yyyy/MM/dd 第二步:
@Configuration
public class TestAutoConfiguration { @Bean
BeanPost beanPost() {
return new BeanPost();
} } class BeanPost implements BeanPostProcessor { @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof FormattingConversionService) {
FormattingConversionService conversionService = (FormattingConversionService)bean;
conversionService.addFormatterForFieldType(Date.class, new DateFormatter("yyyy-MM-dd HH:mm:ss"));
}
return bean;
}
} - 原理:在WebMvcAutoConfiguration中会注入一个FormattingConversionService,用于解析入参的类型转换,FormattingConversionService中维护了一个converters
WebMvcAutoConfiguration {
@Bean
@Override
public FormattingConversionService mvcConversionService() {
WebConversionService conversionService = new WebConversionService(
this.mvcProperties.getDateFormat());
addFormatters(conversionService);
return conversionService;
}
} WebMvcProperties{
private String dateFormat;
...
} FormattingConversionService ...{
private final GenericConversionService.Converters converters = new GenericConversionService.Converters();
...
}
再来看下这个converters对象,里面维护了一个map,map的key是ConvertiblePair,包含了转换的sourceType和targetType,value是ConvertersForPair,里面的list是可以真正用于转换操作的converter的集合,就是上面提到的会linkedList
private static class Converters {
private final Set<GenericConverter> globalConverters;
private final Map<ConvertiblePair, GenericConversionService.ConvertersForPair> converters;
...
} public static final class ConvertiblePair {
private final Class<?> sourceType;
private final Class<?> targetType;
...
} private static class ConvertersForPair {
private final LinkedList<GenericConverter> converters;
...
}
了解到上面的结构之后,就可以清楚的知道当一个请求进入之后的参数绑定过程,但是在调试的过程中发现一个问题,就是这段代码
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional(); Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
} Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
} if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause()); }
} handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
跟进去看到这个方法执行时返回的始终是SimpleTypeConverter,那如果不是这个SimpleTypeConverter会不会跟上面的执行过程不一样呢?
protected TypeConverter getTypeConverter() {
if (getTarget() != null) {
return getInternalBindingResult().getPropertyAccessor();
}
else {
return getSimpleTypeConverter();
}
}
带着这个疑问,继续翻源码,可以看到是因为在上一步的WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);时,target写死了是null,所以才会返回SimpleTypeConverter,这样的话是不是可以有另一种方式传入一个target呢?目前看到的入口就是这里,好像改不了
2.在Date类型的入参上加上注解 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss"),使用的是AnnotationParserConverter,本质上也是使用ParserConverter
- 优点:可以针对不同的参数要求灵活配置
- 缺点:每一个Date类型的参数都需要配置,增加代码量,这种converter属于default,配置了第一种方式也会有这个converter存在,但是它在linkedList中的位置在后面,会被第一种覆盖掉,也不知道是为什么还要加进去.........
public WebConversionService(String dateFormat) {
super(false);
this.dateFormat = StringUtils.hasText(dateFormat) ? dateFormat : null;
if (this.dateFormat != null) {
addFormatters();
}
else {
addDefaultFormatters(this);
}
}
@Override
public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory) {
Class<? extends Annotation> annotationType = getAnnotationType(annotationFormatterFactory);
if (this.embeddedValueResolver != null && annotationFormatterFactory instanceof EmbeddedValueResolverAware) {
((EmbeddedValueResolverAware) annotationFormatterFactory).setEmbeddedValueResolver(this.embeddedValueResolver);
}
Set<Class<?>> fieldTypes = annotationFormatterFactory.getFieldTypes();
for (Class<?> fieldType : fieldTypes) {
addConverter(new AnnotationPrinterConverter(annotationType, annotationFormatterFactory, fieldType));
addConverter(new AnnotationParserConverter(annotationType, annotationFormatterFactory, fieldType));
}
} - 原理:
AnnotationParserConverter{
...
@Override
@SuppressWarnings("unchecked")
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
Annotation ann = targetType.getAnnotation(this.annotationType);
if (ann == null) {
throw new IllegalStateException(
"Expected [" + this.annotationType.getName() + "] to be present on " + targetType);
}
AnnotationConverterKey converterKey = new AnnotationConverterKey(ann, targetType.getObjectType());
GenericConverter converter = cachedParsers.get(converterKey);
if (converter == null) {
Parser<?> parser = this.annotationFormatterFactory.getParser(
converterKey.getAnnotation(), converterKey.getFieldType());
converter = new ParserConverter(this.fieldType, parser, FormattingConversionService.this);
cachedParsers.put(converterKey, converter);
}
return converter.convert(source, sourceType, targetType);
}
} ParserConverter {
...
@Nullable
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
String text = (String) source;
if (!StringUtils.hasText(text)) {
return null;
}
Object result;
try {
result = this.parser.parse(text, LocaleContextHolder.getLocale());
}
catch (IllegalArgumentException ex) {
throw ex;
}
catch (Throwable ex) {
throw new IllegalArgumentException("Parse attempt failed for value [" + text + "]", ex);
}
TypeDescriptor resultType = TypeDescriptor.valueOf(result.getClass());
if (!resultType.isAssignableTo(targetType)) {
result = this.conversionService.convert(result, resultType, targetType);
}
return result;
} }
3.自定义converter,本质就是将自定义的converter放到第一种的那个linkedList的头部
- 优点:统一管理,可以配置多个formatter
- 缺点:目前没有发现,哈哈
@Component
public class DateConverterConfig implements Converter<String, Date> { private static final List<String> formarts = new ArrayList<>(4);
static {
formarts.add("yyyy-MM");
formarts.add("yyyy-MM-dd");
formarts.add("yyyy-MM-dd hh:mm");
formarts.add("yyyy-MM-dd hh:mm:ss");
} @Override
public Date convert(String source) {
String value = source.trim();
if ("".equals(value)) {
return null;
}
if (source.matches("^\\d{4}-\\d{1,2}$")) {
return parseDate(source, formarts.get(0));
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
return parseDate(source, formarts.get(1));
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) {
return parseDate(source, formarts.get(2));
} else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
return parseDate(source, formarts.get(3));
} else {
throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
}
} /**
* 格式化日期
*
* @param dateStr String 字符型日期
* @param format String 格式
* @return Date 日期
*/
public Date parseDate(String dateStr, String format) {
Date date = null;
try {
DateFormat dateFormat = new SimpleDateFormat(format);
date = dateFormat.parse(dateStr);
} catch (Exception e) { }
return date;
} }
通过下面的方式将所有的bean加进去,所以也是可以覆盖的
@Override
public void addFormatters(FormatterRegistry registry) {
for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
registry.addConverter(converter);
}
for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
registry.addConverter(converter);
}
for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
registry.addFormatter(formatter);
}
}
4.通过@InitBinder,绑定CustomDateEditor,此方式可以覆盖第一种,某个类中的可以覆盖全局的,因为底层维护的是一个map,全局的会优先读取,后面的会按照你文件中写的顺序加载
上面的三种方式查找converter是一样的流程editor==null,具体参考org.springframework.beans.TypeConverterDelegate#convertIfNecessary(java.lang.String, java.lang.Object, java.lang.Object, java.lang.Class<T>, org.springframework.core.convert.TypeDescriptor)方法,第四种方式的editor!=null,直接使用editor去解析
全局配置
@ControllerAdvice
public class CoreAspect { @InitBinder
protected void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
} }
某个类单独配置
@RestController
@RequestMapping("/api")
public class TestController { @InitBinder
protected void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
} @RequestMapping("/test")
public Object test(Date date) {
// Date date = new Date();
return date.getTime();
}
}
这种方式每次请求都会去执行initBinder,如果只是一个全局的设置,感觉效率会比第一种方式低,简单的验证了一下,时间上好像差不多,感觉可以忽略掉了,(前三种方式需要从converts里面去查找,里面有一系列的循环操作,第四种虽然每次都去执行initBinder方法,但是最后是直接使用editor去解析的),只是本人自己的认知,如有异议,欢迎反驳
第一种方式 | 第四种方式/类中单独配置 | 第四种方式/全局配置 |
63 75 79 61 74 77 69 80 83 65 76 79 61 73 76 |
67 77 80 67 79 82 57 70 73 60 70 72 57 66 69 |
70 82 85 72 82 85 58 69 72 59 74 80 61 71 74 |
参考链接:https://blog.csdn.net/eumenides_/article/details/79033505
springboot-mvc:入参日期类型转换String->Date的更多相关文章
- Saiku根据入参日期查询出对应的数据(二十)
Saiku根据入参日期查询出对应的数据 之前好像有写过一篇博客关于saiku date range的,现在进一步更新啦!!! 这里的日期筛选会更完善一些,需要提供两个参数 开始日期与结束日期(star ...
- java日期类型转换总结date timestamp calendar string
用Timestamp来记录日期时间还是很方便的,但有时候显示的时候是不需要小数位后面的毫秒的,这样就需要在转换为String时重新定义格式. Timestamp转化为String: S ...
- springBoot controller入参LocalDateTime
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss") @DateTimeForma ...
- spring boot 入参方式
方式: 1).直接写,如public User index2(String name) 2).@RequestParam 与直接写的区别是,可以写默认值. 3).@RequestBody 因为传入的是 ...
- 先查询再插入,改为存储过程,java部分入参出参、mybatisxml【我】
先查询再插入,改为存储过程 create or replace procedure PRO_REVENUE_SI(l_p_cd in Varchar2, l_c_cd in Varchar2, l_p ...
- spring mvc绑定对象String转Date解决入参不能是Date的问题
使用spring的mvc,直接将页面参数绑定到对象中,对象中有属性为Date时会报错,此时需要处理下. 同样的,其他的需要处理的类型也可以用这种方法. 在controller中加入代码 @InitBi ...
- SpringBoot 接收 单个String入参之解决方案
场景: 在做接口时,有的时候,接口入参只需要一个参数,如果将一个参数封装成一个对象很麻烦,故有了以下方式: 思路: spring自带的参数解析器貌似是不具备这个能力的,所有自定义 方式方法: 1.定义 ...
- Java String类型转换成Date日期类型
插入数据库时,存入当前日期,需要格式转换 import java.text.SimpleDateFormat; formatter = new SimpleDateFormat( "yyyy ...
- 日期控件传到后台异常。日期数据格式是 Date 还是 String?
问题:日期控件的时间,传到Controller层直接异常. 前台日期格式:YYYY/MM/DD,后台Java定义的时间类型:Date. 解决: 方法一:原因是Controller层的参数类型定义为 D ...
随机推荐
- liunx pyinotify的安装和使用
介绍此功能是检测目录的操作的事件 1.安装 在百度云盘下载或者在gits上下载安装包 链接:https://pan.baidu.com/s/1Lqt872YEgEo_bNPEnEJMaw 提取码:bj ...
- 如何提高后台服务应用问题的排查效率?日志 VS 远程调试
转眼间,距离Jerry最近一篇文章推送已经过去了一个多月的时间了. 公众号更新的频率降低,不是因为Jerry偷懒,而是由于从春节过后,我所在的SAP成都研究院数字创新空间整个团队,一直在忙一个5月份需 ...
- 【转载】interpolation(插值)和 extrapolation(外推)的区别
根据已有数据以及模型(函数)预测未知区域的函数值,预测的点在已有数据范围内就是interpolation(插值), 范围外就是extrapolation(外推). The Difference Bet ...
- mount -t proc none /proc
linux initrd里的init脚本中的第一句为: mount -t proc /proc /proc 作用是把proc这个虚拟文件系统挂载到/proc目录.这说明initrd需要用到/proc, ...
- Delphi 方法指令字
- 批量kill指定名称的进程
以Airflow举例: ps -ef | grep “airflow" | grep -v grep | cut -c 9-15 | xargs kill -9 分析: ps -ef ...
- MinGW-W64 编译 LLVM 与 Clang
原文: http://blog.csdn.net/happywjh666/article/details/51415723 编译环境: 系统 --win10 64位 gcc -- version 5. ...
- 关于C++ Builder Codegurad 问题的排查。
关于C++ BUILDER6 我目前不知道有什么特别好的内存排查工具.尤其为了对付memory leak, (Eurekalog 这个工具内存泄漏主要针对delphi,BCB配置比较繁琐). 除了BC ...
- P5025 [SNOI2017]炸弹 题解
蒟蒻的第一篇黑题题解(学了这么长时间了才第一道也是没谁了.) 题目链接: Solution: 朴素: 根据题目描述,我们可以处理每一个x节点左右爆炸半径范围内的点,然后模拟一次爆炸 (for),遍历每 ...
- location - URL
1.hash:获取或设置href 属性中跟在数字符号 # 之后的内容 2.跳转页面: 1)location.href 2)location.replace() 3)location.reload(tr ...