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));
}
} - 原理:
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的更多相关文章
- 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 ...
随机推荐
- C++ STL 之 string
#include <iostream> #include <string> using namespace std; // 初始化 void test01() { string ...
- 用Leangoo泳道完美实现Scrum任务看板
转自:https://www.leangoo.com/9568.html 在敏捷开发的实践当中,通过可视化的任务看板来实现团队协同和透明化管理是必不可少的一个实践.通过可视化的任务看板我们可以达到如下 ...
- celery最佳体验
目录 目录 不使用数据库作为 Broker 不要过分关注任务结果 实现优先级任务 应用 Worker 并发池的动态扩展 应用任务预取数 保持任务的幂等性 应用任务超时限制 善用任务工作流 合理应用 a ...
- C和指针--动态内存分配
1.为什么需要使用动态内存分配 数组的元素存储于内存中连续的位置上,当一个数组被声明时,它所需要的内存在编译时就被分配.当你声明数组时,必须用一个编译时常量指定数组的长度.但是,数组的长度常常在运行时 ...
- kotlin字符串比较&空值处理&when表达式
字符串比较: 字符串的比较也是实际中比较常见的,下面来看下它的比较跟java中有啥不同,下面走起! 如我们所预期,其结果: 下面再来: 如果是java,结果肯定是为false,因为"==&q ...
- HDU 5876 补图最短路
开两个集合,一个存储当前顶点可以到达的点,另一个存储当前顶点不能到达的点.如果可以到达,那肯定由该顶点到达是最短的,如果不能,那就留着下一次再判. #include<bits/stdc++.h& ...
- Vue项目中的文件导出
项目中涉及到文件导出,分xml和excel导出.不同的文件导出格式不同,需要根据文件类型判断导出格式. exportAllData(val){ //全部导出 if(!val){ this.export ...
- 如何使用NugetPackageExplorer 创建Nuget发布包,简易版
在上一篇博客中,详细介绍了个人Nuget服务器的搭建.这篇博客中,将详细介绍一下如何使用NugetPackageExplorer工具制作可以发布到Nuget服务器上包. 直奔主题 在开始之前,需要下载 ...
- Mysql数据库备份—-通过LVM快照实现备份还原
一.实验环境 一台测试机:A(172.18.30.1) 操作系统:Centos7 操作对象数据库版本:mariadb-10.2 二.实现目的 从A机器(172.18.30.1)简单搭建数据库,创建测试 ...
- GET /static/plugins/bootstrap/css/bootstrap.css HTTP/1.1" 404 1718
引用的Bootstrap一直不出来,页面中的静态资源无法加载, 报这个错的原因,是因为配置setting时候没有配置好. 后面在setting里面添加下面这段就好了 STATICFILES_DIRS ...