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的更多相关文章

  1. Saiku根据入参日期查询出对应的数据(二十)

    Saiku根据入参日期查询出对应的数据 之前好像有写过一篇博客关于saiku date range的,现在进一步更新啦!!! 这里的日期筛选会更完善一些,需要提供两个参数 开始日期与结束日期(star ...

  2. java日期类型转换总结date timestamp calendar string

    用Timestamp来记录日期时间还是很方便的,但有时候显示的时候是不需要小数位后面的毫秒的,这样就需要在转换为String时重新定义格式.         Timestamp转化为String: S ...

  3. springBoot controller入参LocalDateTime

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss") @DateTimeForma ...

  4. spring boot 入参方式

    方式: 1).直接写,如public User index2(String name) 2).@RequestParam 与直接写的区别是,可以写默认值. 3).@RequestBody 因为传入的是 ...

  5. 先查询再插入,改为存储过程,java部分入参出参、mybatisxml【我】

    先查询再插入,改为存储过程 create or replace procedure PRO_REVENUE_SI(l_p_cd in Varchar2, l_c_cd in Varchar2, l_p ...

  6. spring mvc绑定对象String转Date解决入参不能是Date的问题

    使用spring的mvc,直接将页面参数绑定到对象中,对象中有属性为Date时会报错,此时需要处理下. 同样的,其他的需要处理的类型也可以用这种方法. 在controller中加入代码 @InitBi ...

  7. SpringBoot 接收 单个String入参之解决方案

    场景: 在做接口时,有的时候,接口入参只需要一个参数,如果将一个参数封装成一个对象很麻烦,故有了以下方式: 思路: spring自带的参数解析器貌似是不具备这个能力的,所有自定义 方式方法: 1.定义 ...

  8. Java String类型转换成Date日期类型

    插入数据库时,存入当前日期,需要格式转换 import java.text.SimpleDateFormat; formatter = new SimpleDateFormat( "yyyy ...

  9. 日期控件传到后台异常。日期数据格式是 Date 还是 String?

    问题:日期控件的时间,传到Controller层直接异常. 前台日期格式:YYYY/MM/DD,后台Java定义的时间类型:Date. 解决: 方法一:原因是Controller层的参数类型定义为 D ...

随机推荐

  1. liunx pyinotify的安装和使用

    介绍此功能是检测目录的操作的事件 1.安装 在百度云盘下载或者在gits上下载安装包 链接:https://pan.baidu.com/s/1Lqt872YEgEo_bNPEnEJMaw 提取码:bj ...

  2. 如何提高后台服务应用问题的排查效率?日志 VS 远程调试

    转眼间,距离Jerry最近一篇文章推送已经过去了一个多月的时间了. 公众号更新的频率降低,不是因为Jerry偷懒,而是由于从春节过后,我所在的SAP成都研究院数字创新空间整个团队,一直在忙一个5月份需 ...

  3. 【转载】interpolation(插值)和 extrapolation(外推)的区别

    根据已有数据以及模型(函数)预测未知区域的函数值,预测的点在已有数据范围内就是interpolation(插值), 范围外就是extrapolation(外推). The Difference Bet ...

  4. mount -t proc none /proc

    linux initrd里的init脚本中的第一句为: mount -t proc /proc /proc 作用是把proc这个虚拟文件系统挂载到/proc目录.这说明initrd需要用到/proc, ...

  5. Delphi 方法指令字

  6. 批量kill指定名称的进程

     以Airflow举例: ps -ef | grep “airflow" | grep -v grep | cut -c 9-15 | xargs kill -9   分析: ps -ef  ...

  7. MinGW-W64 编译 LLVM 与 Clang

    原文: http://blog.csdn.net/happywjh666/article/details/51415723 编译环境: 系统 --win10 64位 gcc -- version 5. ...

  8. 关于C++ Builder Codegurad 问题的排查。

    关于C++ BUILDER6 我目前不知道有什么特别好的内存排查工具.尤其为了对付memory leak, (Eurekalog 这个工具内存泄漏主要针对delphi,BCB配置比较繁琐). 除了BC ...

  9. P5025 [SNOI2017]炸弹 题解

    蒟蒻的第一篇黑题题解(学了这么长时间了才第一道也是没谁了.) 题目链接: Solution: 朴素: 根据题目描述,我们可以处理每一个x节点左右爆炸半径范围内的点,然后模拟一次爆炸 (for),遍历每 ...

  10. location - URL

    1.hash:获取或设置href 属性中跟在数字符号 # 之后的内容 2.跳转页面: 1)location.href 2)location.replace() 3)location.reload(tr ...