栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

严查前后端日期格式传值问题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

严查前后端日期格式传值问题

开心接活

一天,小码鸽接到一个需求,需求大概是这样的:要求统计某个时间段的人工成本,前端传入统计的开始时间和结束时间,还有成本类型等一些其他的查询条件,后端根据前端的查询条件,返回汇总的人工成本。哎哟,这个需求很简单啦。阿鸽内心窃喜,马上撸起了袖子,一顿操作…

问题浮现

阿鸽信心满满的的敲完了代码,觉得已经万无一失,准备和前端联调,可是接下来的一幕,瞬间让他上扬的嘴角僵持住了,他觉得不可能,他心态崩了。。。
控制台打印出了如下信息:

WARN 14676 --- [nio-8763-exec-9] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.Date` from String "2022-07-01 00:00:00": not a valid representation (error: Failed to parse Date value '2022-07-01 00:00:00': Cannot parse date "2022-07-01 00:00:00": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX', parsing fails (leniency? null)); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "2022-07-01 00:00:00": not a valid representation (error: Failed to parse Date value '2022-07-01 00:00:00': Cannot parse date "2022-07-01 00:00:00": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX', parsing fails (leniency? null))
 at [Source: (PushbackInputStream); line: 2, column: 14] (through reference chain: com.example.nacosconsumer.demo.dto.LaborCostQueryCondition["endDate"])]
原因

前端传递yy-MM-dd HH:mm:ss格式的时间字符串,后端只使用Date无法直接类型转换。这究竟是为啥呢,让我们开始定位问题。首先从DispathcherServlet类开始,我们来看看请求的处理过程。在DispathcherServlet类的doService方法中调用doDispatch方法,这个方法首先确定处理请求的handler。

返回的mappedHandler变量是一个HandlerExecutionChain类型的对象,大概瞄一眼是啥东西。

嗯?注释不是说确定handler的吗,怎么还夹带私货呢,不讲武德。那好吧,返回了handler加上拦截器信息,继续往下走。

哎,看注释这是要调用handler真正去处理请求了,点进去看下是不是真的。

这是个接口,有五个实现了该接口的类,有四个重写了handle方法,有这么多劳动力在这,那先看下的具体打工人吧,勤勤恳恳的"打工人"RequestMappingHandlerAdapter登场了。

哎呦,不错哟,RequestMappingHandlerAdapter一看就是妥妥的富二代了。


虽然从AbstractHandlerMethodAdapter那继承来了handle方法,但是RequestMappingHandlerAdapter需要自己去实现handleInternal方法才能干活RequestMappingHandlerAdapter说这不是坑娃嘛,不过没事,一代人有一代人的责任,这次我来上吧,经过它的不屑努力,终于实现了属于自己的一套绝技,而这套绝技的秘诀就是invokeHandlerMethod。

我们直奔主题,来揭开这神秘的面纱

凭直觉和语义推测,这invokeHandlerMethod方法里的invokeAndHandle调用的后面,应该就是真相了。这个invokeAndHandle方法在ServletInvocableHandlerMethod对象里,然后它又调用了InvocableHandlerMethod类的invokeForRequest,原来最后的boss是这个InvocableHandlerMethod



找到了最后boss,开始实施最后的逮捕。这个invokeForRequest方法里的getMethodArgumentValues方法调用很可疑,看它的名字就知道它有问题,锁定一号目标。

进一步调查发现它会委托参数解析器对参数进行解析,这次他请来的帮手是RequestResponseBodyMethodProcessor,由他来负责具体的解析任务。

完成这个解析任务依靠readWithMessageConverters方法调用。

这个方法还有个双胞胎兄弟,我们来认识认识

@Nullable
protected  Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
		MediaType contentType;
		boolean noContentType = false;
		try {
			contentType = inputMessage.getHeaders().getContentType();
		}
		catch (InvalidMediaTypeException ex) {
			throw new HttpMediaTypeNotSupportedException(ex.getMessage());
		}
		if (contentType == null) {
			noContentType = true;
			contentType = MediaType.APPLICATION_OCTET_STREAM;
		}

		Class contextClass = parameter.getContainingClass();
		Class targetClass = (targetType instanceof Class ? (Class) targetType : null);
		if (targetClass == null) {
			ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
			targetClass = (Class) resolvableType.resolve();
		}

		HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
		Object body = NO_VALUE;

		EmptyBodyCheckingHttpInputMessage message;
		try {
			message = new EmptyBodyCheckingHttpInputMessage(inputMessage);

			for (HttpMessageConverter converter : this.messageConverters) {
				Class> converterType = (Class>) converter.getClass();
				GenericHttpMessageConverter genericConverter =
						(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter) converter : null);
				if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
						(targetClass != null && converter.canRead(targetClass, contentType))) {
					if (message.hasBody()) {
						HttpInputMessage msgToUse =
								getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
						body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
								((HttpMessageConverter) converter).read(targetClass, msgToUse));
						body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
					}
					else {
						body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
					}
					break;
				}
			}
		}
		catch (IOException ex) {
			throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
		}

		if (body == NO_VALUE) {
			if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
					(noContentType && !message.hasBody())) {
				return null;
			}
			throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
		}

		MediaType selectedContentType = contentType;
		Object theBody = body;
		LogFormatUtils.traceDebug(logger, traceOn -> {
			String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
			return "Read "" + selectedContentType + "" to [" + formatted + "]";
		});

		return body;
	}

可惜这次作案没有成功,因为在解析请求体时抛出了最开始的小码鸽遇到的异常,导致了这次请求处理任务失败了,抛出异常的位置是这里


表面上看是springmvc无法将日期类型的字符串,转换为Date类型,实质上是readWithMessageConverters这里解析请求体出错了。

解决方法
  1. 前端传递数据时使用yy-MM-dd格式的时间字符串,后端用Date可以直接接收
  2. 在pojo实体的日期属性上加上@JsonFormat注解
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1027275.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号