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));
- }
- }
- public WebConversionService(String dateFormat) {
- 原理:
- 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;
- }
- }
- AnnotationParserConverter{
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 ...
随机推荐
- 【Struts2】简介及入门
一.概述 二.Struts2 快速入门程序 2.1 开发流程比较 2.2 引入依赖 2.2 创建jsp页面 2.3 在web.xml中配置前端控制器 2.4 创建struts.xml配置文件 2.4 ...
- Maven 基础概念
Project:任何你想构建的事务Maven都可以认为它们是工程,这些工程被定义为工程对象模型(project Object Model POM) 一个工程可以依赖其他的工程,一个工程也可以由多个子工 ...
- 01 js数据类型
1.不管什么语言,上来就应该是数据类型了.js也不例外.那么基本的数据类型我们有,boolean, number, string, null, undefine, symbol, object, fu ...
- 寻找一组数中最大的K个数
对于"从一组数中挑出最大的K个数"这个在面试中经常会遇到,所以这次好好的去解析它,而当拿到这个问题时第一时间能想到解法就是:先对数据进行排序,然后再取最大的K个元素,当然这思路没毛 ...
- linux基础_使用指令2
1.cat指令 功能:查看文件内容,是以只读的方式打开. 语法:cat [] 要查看的文件 选项: -n:显示行号 末尾加 | more:分页 使用细节: cat只能浏览文件,而不能修改文件,为了浏览 ...
- VMware虚拟机CentOS与宿主机共享目录
正常情况下,在虚拟机CentOS中安装了vmware-tools后,配置完成共享目录,会自动在/mnt/hgfs下面出现共享目录. 如果该目录为空,并且通过命令:vmware-hgfsclient 的 ...
- ACM-ICPC 2017 沈阳赛区现场赛 M. Wandering Robots && HDU 6229(思维+期望)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6229 参考题解:https://blog.csdn.net/lifelikes/article/det ...
- 010_linuxC++之_运算符重载
(一)运算符重载:运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型. (二)实现类不同对象里中变量的相加 (三)程序 #include <iostream> ...
- java实现上传文件夹
我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用. 首先我们需要了解的是上传文件三要素: 1.表单提交方式:post (get方式提交有大小 ...
- php+上传超大文件
demo下载:http://t.cn/Ai9p3CKQ 教程:http://t.cn/Aipg9uUK 一提到大文件上传,首先想到的是啥??? 没错,就是修改php.ini文件里的上传限制,那就是up ...