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. 安卓SharedPreferences类的使用

    package com.lidaochen.phonecall; import android.content.Intent; import android.content.SharedPrefere ...

  2. openresty 阶段说明

    开发中常用的7阶段 set_by_lua*: 流程分支处理判断变量初始化 rewrite_by_lua*: 转发.重定向.缓存等功能(例如特定请求代理到外网) access_by_lua*: IP 准 ...

  3. python文件操作:字符编码与文件处理

    一.字符编码 二.文件处理 一.字符编码 储备知识点: 1. 计算机系统分为三层: 应用程序 操作系统 计算机硬件 2. 运行python程序的三个步骤 1. 先启动python解释器 2. 再将py ...

  4. okhttp异步请求流程和源码分析

    在上一次[http://www.cnblogs.com/webor2006/p/8023967.html]中对同步请求进行了详细分析,这次来分析一下异步请求,而关于异步请求和同步请求其使用方式基本上差 ...

  5. cas多方式登录相关知识点的总结

    知识点: cas多表单登录(在用户名,密码的基础上,增加短信验证码登录) 自定义认证策略 自定义字段添加为空校验的错误信息 Controller层接口的调用 一:场景 项目涉及到的业务是,在原cas用 ...

  6. [原创]在Windows平台使用msys2、mingw64和vscode编写和调试C/C++代码

    相关名词就不解释了,这里主要讲讲在vscode里怎么配,这里假设大家相关工具已经装好. 题外话:里面的大多数坑都是windows平台和linux平台的差异造成的,如果在linux平台配置,应该会顺利很 ...

  7. MySQL基准测试和sysbench工具

    参考https://www.cnblogs.com/kismetv/archive/2017/09/30/7615738.html 一.基准测试的作用 sysbench是一个开源的.模块化的.跨平台的 ...

  8. flex布局详解

    1.背景介绍 传统的布局解决方案,基于盒状模型,依赖 display 属性 + position属性 + float属性.它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现. 2009年,W3C ...

  9. (二)线程Thread中的方法详解

    1.start() start()方法的作用讲得直白点就是通知"线程规划器",此线程可以运行了,正在等待CPU调用线程对象得run()方法,产生一个异步执行的效果.通过start( ...

  10. Thread setUncaughtExceptionHandler

    setUncaughtExceptionHandler 用于获取线程运行时异常 线程在执行时是不能抛出 checked 异常的,IDE 只会提示你用 try-catch 包裹起来.因此主线程无法直接获 ...