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. 用Leangoo泳道完美实现Scrum任务看板

    转自:https://www.leangoo.com/9568.html 在敏捷开发的实践当中,通过可视化的任务看板来实现团队协同和透明化管理是必不可少的一个实践.通过可视化的任务看板我们可以达到如下 ...

  2. DICOM文件修改方法

    /// <summary> /// 读取dicom文件 /// </summary> /// <param name="srcdirectory"&g ...

  3. asp.net网站部署在云服务器windows server 2008上

    搭建一个网站需要以下4个准备: 1.域名解析 2.(云)服务器 3.数据库 4.网站代码 其中1可以可以去DNSPOD申请,同时需要进行备案,在上面就都可以完成.2用的是阿里云服务器windows s ...

  4. 12个提高Java程序员工作效率的工具

    Java开发者常常都会想办法如何更快地编写Java代码,让开发过程变得更加轻松,更加高效.目前,市面上涌现出越来越多的高效编程工具.团长总结了几个常用的工具,其中包含了大多数开发人员已经使用.正在使用 ...

  5. 转载-对于Python中@property的理解和使用

    原文链接:https://blog.csdn.net/u013205877/article/details/77804137 重看狗书,看到对User表定义的时候有下面两行 @property def ...

  6. Hadoop_23_MapReduce倒排索引实现

    1.1.倒排索引  根据属性的值来查找记录.这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址.由于不是由记录来确 定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(invert ...

  7. ServiceLoader在SPI中的重要作用分析

    对于线程上下文类加载器在之前已经对它进行了详细的理论化的学习,其中对于这个类加载器应用最多的也就是在SPI场合下用来打破双亲委托机制,如之前所描述的: 这次举一个具体的例子来进一步的加深对线程上下文类 ...

  8. UESTC 2016 Summer Training #1 J - Objects Panel (A) 按条件遍历树

    #include <iostream> #include <cstdio> #include <vector> using namespace std; typed ...

  9. python: json模块 --JSON编码和解码

    json 源代码: Lib/json/__init__.py json.dump() import json numbers = [1, 2, 3, 4] with open('linshi.py', ...

  10. CF1009F Dominant Indices——长链剖分优化DP

    原题链接 \(EDU\)出一道长链剖分优化\(dp\)裸题? 简化版题意 问你每个点的子树中与它距离为多少的点的数量最多,如果有多解,最小化距离 思路 方法1. 用\(dsu\ on\ tree\)做 ...