4种方式:

1.通过在application.ym中配置 spring.mvc.data-format: yyyy-MM-dd HH:mm:ss ,使用的是ParserConverter

  • 优点:简单的配置就可以,很方便
  • 缺点:只能设置一种格式生效(ps:可以通过addFormatterForFieldType方法设置多种格式,但是它内部维护的是一个linkedList,会将最后设置的addFirst,查找时从头开始找,找到即返回,所以生效的始终是最后设置的那个格式),参考如下配置,当传入yyyy/MM/dd这种格式时会报错
    1. 第一步:
    2. spring:
    3. mvc:
    4. date-format: yyyy/MM/dd
    5.  
    6. 第二步:
    7. @Configuration
    8. public class TestAutoConfiguration {
    9.  
    10. @Bean
    11. BeanPost beanPost() {
    12. return new BeanPost();
    13. }
    14.  
    15. }
    16.  
    17. class BeanPost implements BeanPostProcessor {
    18.  
    19. @Override
    20. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    21. if (bean instanceof FormattingConversionService) {
    22. FormattingConversionService conversionService = (FormattingConversionService)bean;
    23. conversionService.addFormatterForFieldType(Date.class, new DateFormatter("yyyy-MM-dd HH:mm:ss"));
    24. }
    25. return bean;
    26. }
    27. }
  • 原理:在WebMvcAutoConfiguration中会注入一个FormattingConversionService,用于解析入参的类型转换,FormattingConversionService中维护了一个converters
  1. WebMvcAutoConfiguration {
  2. @Bean
  3. @Override
  4. public FormattingConversionService mvcConversionService() {
  5. WebConversionService conversionService = new WebConversionService(
  6. this.mvcProperties.getDateFormat());
  7. addFormatters(conversionService);
  8. return conversionService;
  9. }
  10. }
  11.  
  12. WebMvcProperties{
  13. private String dateFormat;
  14.   ...
  15. }
  16.  
  17. FormattingConversionService ...{
  18.   private final GenericConversionService.Converters converters = new GenericConversionService.Converters();
  19.   ...
  20. }

再来看下这个converters对象,里面维护了一个map,map的key是ConvertiblePair,包含了转换的sourceType和targetType,value是ConvertersForPair,里面的list是可以真正用于转换操作的converter的集合,就是上面提到的会linkedList

  1. private static class Converters {
  2. private final Set<GenericConverter> globalConverters;
  3. private final Map<ConvertiblePair, GenericConversionService.ConvertersForPair> converters;
  4. ...
  5. }
  6.  
  7. public static final class ConvertiblePair {
  8. private final Class<?> sourceType;
  9. private final Class<?> targetType;
  10. ...
  11. }
  12.  
  13. private static class ConvertersForPair {
  14. private final LinkedList<GenericConverter> converters;
  15. ...
  16. }

了解到上面的结构之后,就可以清楚的知道当一个请求进入之后的参数绑定过程,但是在调试的过程中发现一个问题,就是这段代码

  1. @Override
  2. @Nullable
  3. public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
  4. NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
  5.  
  6. NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
  7. MethodParameter nestedParameter = parameter.nestedIfOptional();
  8.  
  9. Object resolvedName = resolveStringValue(namedValueInfo.name);
  10. if (resolvedName == null) {
  11. throw new IllegalArgumentException(
  12. "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
  13. }
  14.  
  15. Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
  16. if (arg == null) {
  17. if (namedValueInfo.defaultValue != null) {
  18. arg = resolveStringValue(namedValueInfo.defaultValue);
  19. }
  20. else if (namedValueInfo.required && !nestedParameter.isOptional()) {
  21. handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
  22. }
  23. arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
  24. }
  25. else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
  26. arg = resolveStringValue(namedValueInfo.defaultValue);
  27. }
  28.  
  29. if (binderFactory != null) {
  30. WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
  31. try {
  32. arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
  33. }
  34. catch (ConversionNotSupportedException ex) {
  35. throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
  36. namedValueInfo.name, parameter, ex.getCause());
  37. }
  38. catch (TypeMismatchException ex) {
  39. throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
  40. namedValueInfo.name, parameter, ex.getCause());
  41.  
  42. }
  43. }
  44.  
  45. handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
  46. return arg;
  47. }

跟进去看到这个方法执行时返回的始终是SimpleTypeConverter,那如果不是这个SimpleTypeConverter会不会跟上面的执行过程不一样呢?

  1. protected TypeConverter getTypeConverter() {
  2. if (getTarget() != null) {
  3. return getInternalBindingResult().getPropertyAccessor();
  4. }
  5. else {
  6. return getSimpleTypeConverter();
  7. }
  8. }

带着这个疑问,继续翻源码,可以看到是因为在上一步的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中的位置在后面,会被第一种覆盖掉,也不知道是为什么还要加进去.........  
    1. public WebConversionService(String dateFormat) {
    2. super(false);
    3. this.dateFormat = StringUtils.hasText(dateFormat) ? dateFormat : null;
    4. if (this.dateFormat != null) {
    5. addFormatters();
    6. }
    7. else {
    8. addDefaultFormatters(this);
    9. }
    10. }
    11. @Override
    12. public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory) {
    13. Class<? extends Annotation> annotationType = getAnnotationType(annotationFormatterFactory);
    14. if (this.embeddedValueResolver != null && annotationFormatterFactory instanceof EmbeddedValueResolverAware) {
    15. ((EmbeddedValueResolverAware) annotationFormatterFactory).setEmbeddedValueResolver(this.embeddedValueResolver);
    16. }
    17. Set<Class<?>> fieldTypes = annotationFormatterFactory.getFieldTypes();
    18. for (Class<?> fieldType : fieldTypes) {
    19. addConverter(new AnnotationPrinterConverter(annotationType, annotationFormatterFactory, fieldType));
    20. addConverter(new AnnotationParserConverter(annotationType, annotationFormatterFactory, fieldType));
    21. }
    22. }
  • 原理:
    1. AnnotationParserConverter{
    2. ...
    3. @Override
    4. @SuppressWarnings("unchecked")
    5. @Nullable
    6. public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
    7. Annotation ann = targetType.getAnnotation(this.annotationType);
    8. if (ann == null) {
    9. throw new IllegalStateException(
    10. "Expected [" + this.annotationType.getName() + "] to be present on " + targetType);
    11. }
    12. AnnotationConverterKey converterKey = new AnnotationConverterKey(ann, targetType.getObjectType());
    13. GenericConverter converter = cachedParsers.get(converterKey);
    14. if (converter == null) {
    15. Parser<?> parser = this.annotationFormatterFactory.getParser(
    16. converterKey.getAnnotation(), converterKey.getFieldType());
    17. converter = new ParserConverter(this.fieldType, parser, FormattingConversionService.this);
    18. cachedParsers.put(converterKey, converter);
    19. }
    20. return converter.convert(source, sourceType, targetType);
    21. }
    22. }
    23.  
    24. ParserConverter {
      ...
    25. @Nullable
    26. public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
    27. String text = (String) source;
    28. if (!StringUtils.hasText(text)) {
    29. return null;
    30. }
    31. Object result;
    32. try {
    33. result = this.parser.parse(text, LocaleContextHolder.getLocale());
    34. }
    35. catch (IllegalArgumentException ex) {
    36. throw ex;
    37. }
    38. catch (Throwable ex) {
    39. throw new IllegalArgumentException("Parse attempt failed for value [" + text + "]", ex);
    40. }
    41. TypeDescriptor resultType = TypeDescriptor.valueOf(result.getClass());
    42. if (!resultType.isAssignableTo(targetType)) {
    43. result = this.conversionService.convert(result, resultType, targetType);
    44. }
    45. return result;
    46. }
    47.  
    48. }

3.自定义converter,本质就是将自定义的converter放到第一种的那个linkedList的头部

  • 优点:统一管理,可以配置多个formatter
  • 缺点:目前没有发现,哈哈
  1. @Component
  2. public class DateConverterConfig implements Converter<String, Date> {
  3.  
  4. private static final List<String> formarts = new ArrayList<>(4);
  5. static {
  6. formarts.add("yyyy-MM");
  7. formarts.add("yyyy-MM-dd");
  8. formarts.add("yyyy-MM-dd hh:mm");
  9. formarts.add("yyyy-MM-dd hh:mm:ss");
  10. }
  11.  
  12. @Override
  13. public Date convert(String source) {
  14. String value = source.trim();
  15. if ("".equals(value)) {
  16. return null;
  17. }
  18. if (source.matches("^\\d{4}-\\d{1,2}$")) {
  19. return parseDate(source, formarts.get(0));
  20. } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) {
  21. return parseDate(source, formarts.get(1));
  22. } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) {
  23. return parseDate(source, formarts.get(2));
  24. } else if (source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")) {
  25. return parseDate(source, formarts.get(3));
  26. } else {
  27. throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
  28. }
  29. }
  30.  
  31. /**
  32. * 格式化日期
  33. *
  34. * @param dateStr String 字符型日期
  35. * @param format String 格式
  36. * @return Date 日期
  37. */
  38. public Date parseDate(String dateStr, String format) {
  39. Date date = null;
  40. try {
  41. DateFormat dateFormat = new SimpleDateFormat(format);
  42. date = dateFormat.parse(dateStr);
  43. } catch (Exception e) {
  44.  
  45. }
  46. return date;
  47. }
  48.  
  49. }

通过下面的方式将所有的bean加进去,所以也是可以覆盖的

  1. @Override
  2. public void addFormatters(FormatterRegistry registry) {
  3. for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
  4. registry.addConverter(converter);
  5. }
  6. for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
  7. registry.addConverter(converter);
  8. }
  9. for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
  10. registry.addFormatter(formatter);
  11. }
  12. }

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去解析

  1. 全局配置
    @ControllerAdvice
  2. public class CoreAspect {
  3.  
  4. @InitBinder
  5. protected void initBinder(WebDataBinder binder) {
  6. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  7. binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
  8. }
  9.  
  10. }
  11. 某个类单独配置
  12. @RestController
  13. @RequestMapping("/api")
  14. public class TestController {
  15.  
  16. @InitBinder
  17. protected void initBinder(WebDataBinder binder) {
  18. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
  19. binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
  20. }
  21.  
  22. @RequestMapping("/test")
  23. public Object test(Date date) {
  24. // Date date = new Date();
  25. return date.getTime();
  26. }
  27. }

这种方式每次请求都会去执行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. 【Struts2】简介及入门

    一.概述 二.Struts2 快速入门程序 2.1 开发流程比较 2.2 引入依赖 2.2 创建jsp页面 2.3 在web.xml中配置前端控制器 2.4 创建struts.xml配置文件 2.4 ...

  2. Maven 基础概念

    Project:任何你想构建的事务Maven都可以认为它们是工程,这些工程被定义为工程对象模型(project Object Model POM) 一个工程可以依赖其他的工程,一个工程也可以由多个子工 ...

  3. 01 js数据类型

    1.不管什么语言,上来就应该是数据类型了.js也不例外.那么基本的数据类型我们有,boolean, number, string, null, undefine, symbol, object, fu ...

  4. 寻找一组数中最大的K个数

    对于"从一组数中挑出最大的K个数"这个在面试中经常会遇到,所以这次好好的去解析它,而当拿到这个问题时第一时间能想到解法就是:先对数据进行排序,然后再取最大的K个元素,当然这思路没毛 ...

  5. linux基础_使用指令2

    1.cat指令 功能:查看文件内容,是以只读的方式打开. 语法:cat [] 要查看的文件 选项: -n:显示行号 末尾加 | more:分页 使用细节: cat只能浏览文件,而不能修改文件,为了浏览 ...

  6. VMware虚拟机CentOS与宿主机共享目录

    正常情况下,在虚拟机CentOS中安装了vmware-tools后,配置完成共享目录,会自动在/mnt/hgfs下面出现共享目录. 如果该目录为空,并且通过命令:vmware-hgfsclient 的 ...

  7. ACM-ICPC 2017 沈阳赛区现场赛 M. Wandering Robots && HDU 6229(思维+期望)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6229 参考题解:https://blog.csdn.net/lifelikes/article/det ...

  8. 010_linuxC++之_运算符重载

    (一)运算符重载:运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型. (二)实现类不同对象里中变量的相加 (三)程序 #include <iostream> ...

  9. java实现上传文件夹

    我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用. 首先我们需要了解的是上传文件三要素: 1.表单提交方式:post (get方式提交有大小 ...

  10. php+上传超大文件

    demo下载:http://t.cn/Ai9p3CKQ 教程:http://t.cn/Aipg9uUK 一提到大文件上传,首先想到的是啥??? 没错,就是修改php.ini文件里的上传限制,那就是up ...