关于SpringBoot中Swagger使用post请求传入时间类型的问题

1、问题描述

在某个功能中,后端使用@RequestBody接收前端的参数

@ApiOperation("/获取时间信息")
    @PostMapping("/getDate")
    public Object getDate(@RequestBody User user){
        System.out.println("action method: " + user.getDate().getTime());
        return user.getDate().getTime()+"";
    }

在swwager中测试接口

{
  "age": 36,
  "date": "2022-10-26 10:21:04",
  "name": "张三"
}

 实体类

@Data
public class User {
    private String name;

    private Integer age;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date date;
}

后端服务报错如下:Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.Date` from String "2022-10-26 10:21:04": not a valid representation (error: Failed to parse Date value '2022-10-26 10:21:04': Cannot parse date "2022-10-26 10:21:04": 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-10-26 10:21:04": not a valid representation (error: Failed to parse Date value '2022-10-26 10:21:04': Cannot parse date "2022-10-26 10:21:04": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX', parsing fails (leniency? null))<EOL> at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 3, column: 11] (through reference chain: com.prj.pojo.entity.User["date"])] 

2、代码环境:

springboot 2.6.9,java8,idea 2020.3

3、代码跟踪:

大家都知道,所有的请求后端的逻辑都会进入DispatcherServlet的doDispatch方法

逐步断点调试如下图,发现invocableMethod就是我的controller,继续断点进入

 ServletInvocableHandlerMethod的invokeAndHandle方法就是执行controller的逻辑

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
    // 解析参数并执行controller逻辑
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
		setResponseStatus(webRequest);
	if (returnValue == null) {
		if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
			disableContentCachingIfNecessary(webRequest);
			mavContainer.setRequestHandled(true);
			return;
		}
	}
	else if (StringUtils.hasText(getResponseStatusReason())) {
		mavContainer.setRequestHandled(true);
		return;
	}

	mavContainer.setRequestHandled(false);
	Assert.state(this.returnValueHandlers != null, "No return value handlers");
	// 处理返回值
	try {
		this.returnValueHandlers.handleReturnValue(
					returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	}catch (Exception ex) {
		if (logger.isTraceEnabled()){
		logger.trace(formatErrorForReturnValue(returnValue), ex);
		}
		throw ex;
	}
}

 进入invokeForRequest方法

	@Nullable
	public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
//解析参数
		Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
		if (logger.isTraceEnabled()) {
			logger.trace("Arguments: " + Arrays.toString(args));
		}
//执行controller方法
		return doInvoke(args);
	}

 因为是找寻解析参数失败的原因,所以进入getMethodArgumentValues方法,第179行是具体的解析逻辑,进入

 继续断点进入,在第139行的validateIfApplicable(binder, parameter);是参数校验的逻辑,如果controller层的接口有@Validated的注解,会在这里进行判断。

 

 在下图中的for循环中的genericConverter.canRead是HttpMessageConverter的方法,用来判断能否转为java对象

 最后发现进入AbstractJackson2HttpMessageConverter类

该类也是HttpMessageConverter的子类

中间是一些判断字符集,继续断点进入

 断点进入后发现ObjectMapper是com.fasterxml.jackson.databind包下的

 一路断点到BeanDeserializer类,该方法较长,从386行到398行是一个dowhile循环

 在p变量中找到了我们在swagger中传入的参数类型,dowhile中会挨个遍历,然后转换为对应的实体类属性类型

 重点来了,当propName是date时,断点进入

 

 

 

 下图中df其实是StdDateFormat

 打开StdDateFormat类,发现仅支持这几种时间转换格式。

我在swagger传入的2022-10-26 10:21:04类型,因此直接报错

 4、解决办法:

既然找到问题了,解决也就很容易了

@Configuration
public class LocalDateTimeSerializerConfig {

    private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";

    @Bean
    public Jackson2ObjectMapperBuilder mapperBuilder() {
        Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder = new Jackson2ObjectMapperBuilder();
        jackson2ObjectMapperBuilder.dateFormat(new SimpleDateFormat(DATE_PATTERN))
                .serializerByType(LocalDateTime.class, localDateTimeSerializer())
                .deserializerByType(LocalDateTime.class, localDateTimeDeserializer());
        return jackson2ObjectMapperBuilder;
    }
    @Bean
    public LocalDateTimeSerializer localDateTimeSerializer() {
        return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_PATTERN));
    }

    @Bean
    public LocalDateTimeDeserializer localDateTimeDeserializer() {
        return new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DATE_PATTERN));
    }
}

 再看下图中df的值,已经变成SimpleDateFormat了,因为在上面的mapperBuilder()方法中,我指定了SimpleDateFormat和对应的解析格式。

 

 下面第一张图是出现转换失败时的MappingJackson2HttpMessageConverter,

第二张图是添加了LocalDateTimeSerializerConfig的MappingJackson2HttpMessageConverter,很明显,第二张图已经替换为我添加的SimpleDateFormat转换器了。

springboot的思想是约定优于配置,也就是说,springboot默认帮我们配好了spring mvc的Converter,如果我们没有自定义Converter的话,那么框架就会帮我们创建一个,如果我们有自定义的话,那么springboot就直接使用我们所注册的bean进行绑定

以上摘自https://blog.csdn.net/qq906627950/article/details/79503801

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值