【spring boot】Controller @RequestMapping 数据绑定:接收 Date 类型参数时遇错,将 String 类型的参数转换成 Date 类型

前言

  • spring boot 2.1.1.RELEASE

遇错

Failed to convert value of type ‘java.lang.String’ to required type ‘java.util.Date’

Controller 中的接收 Date 类型参数时,会遇到这个错误。

@RequestMapping(path="/list", produces="text/plain;charset=UTF-8")
public @ResponseBody String list(
		@RequestParam(name = "beginTime", required = false)Date beginTime,
		@RequestParam(name = "endTime", required = false)Date endTime) {
	...
}

遇错时,请求是这样的:

http://xxx/xxx/list?begingTime=2020-10-01 00:00:00&endTime=2020-11-01 00:00:00

解决办法

告知 spring boot 如何将 String 类型的参数转换成 Date 类型。

@RequestMapping(path="/list", produces="text/plain;charset=UTF-8")
public @ResponseBody String list(
		@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
		@RequestParam(name = "beginTime", required = false)Date beginTime,
		@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
		@RequestParam(name = "endTime", required = false)Date endTime) {
	...
}

扩大一下影响面的解决办法

上面的方法是一个点一个点的将 String 类型的参数转换成 Date 类型。这里来个一条线一条线的将 String 类型的参数转换成 Date 类型。

list 方法所在的 Controller 中, 添加下面的方法:

@InitBinder
public void initBinder(WebDataBinder binder, WebRequest request) {
	//转换日期 注意这里的转化要和传进来的字符串的格式一直 如2015-9-9 就应该为yyyy-MM-dd
	SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	//是否严格解析时间 false则严格解析 true宽松解析
	dateFormat.setLenient(false);
	// CustomDateEditor为自定义日期编辑器
	binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}

对同一 Controller 下的所有 Request 都会起作用, 有一个 initBinder 就够了。

改进一下解决办法

如果你想让下面的请求都不报错,就必须改进一下:

http://xxx/xxx/list?begingTime=2020-10-01 00:00:00&endTime=2020-11-01 00:00:00
http://xxx/xxx/list?begingTime=2020-10-01&endTime=2020-11-01
http://xxx/xxx/list?begingTime=2020-10-01 00&endTime=2020-11-01 00
http://xxx/xxx/list?begingTime=2020-10-01 00:00&endTime=2020-11-01 00:00

此种情况下,必须自己写一个 String 类型的参数转换成 Date 类型的实现类,如下:

import java.beans.PropertyEditorSupport;
import java.text.ParseException;
import java.util.Date;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

public class MultiformatDateEditor extends PropertyEditorSupport {
	private final String[] dateFormatPatterns;
	
	public MultiformatDateEditor(String... patterns) {
		this.dateFormatPatterns = patterns;
	}
	
	@Override
	public void setAsText(@Nullable String text) throws IllegalArgumentException {
		if (!StringUtils.hasText(text)) {
			// Treat empty String as null value.
			setValue(null);
		}
		else {
			try {
				setValue(DateUtils.parseDate(text, this.dateFormatPatterns));
			}
			catch (ParseException ex) {
				throw new IllegalArgumentException("Could not parse date: " + ex.getMessage(), ex);
			}
		}
	}
	
	@Override
	public String getAsText() {
		Date value = (Date) getValue();
		return (value != null ? DateFormatUtils.format(value, this.dateFormatPatterns[0]) : "");
	}
}

然后改造一下 initBinder 方法:

@InitBinder
public void initBinder(WebDataBinder binder, WebRequest request) {
	String[] dateFormatPatterns = {
				"yyyy-MM-dd HH:mm:ss", 
				"yyyy-MM-dd HH:mm", 
				"yyyy-MM-dd HH", 
				"yyyy-MM-dd"};
	binder.registerCustomEditor(Date.class, new MultiformatDateEditor(dateFormatPatterns));
}

作用到整个项目

尽管改造过一次了,还得每个需要的地方都要加一次,还是有点儿麻烦。本方法能够作用到整个项目,将 String 类型的参数转换成 Date 类型。

改用 ControllerAdvice 给每个 Controller 都加上 initBinder 。

import java.util.Date;

import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.context.request.WebRequest;

@ControllerAdvice
public class GobalWebDataBinder {
	@InitBinder
    public void initBinder(WebDataBinder binder, WebRequest request) {
		String[] dateFormatPatterns = {
				"yyyy-MM-dd HH:mm:ss", 
				"yyyy-MM-dd HH:mm", 
				"yyyy-MM-dd HH", 
				"yyyy-MM-dd"};
		binder.registerCustomEditor(Date.class, new MultiformatDateEditor(dateFormatPatterns));
    }
}

Spring Boot 专用的方式

Spring Boot 中 WebMvcAutoConfiguration 自动进行了一系列与 Web Mvc 相关的配置,里边也包括添加 type Formatters。

WebMvcAutoConfiguration 下面的 WebMvcAutoConfigurationAdapter 下面的 addFormatters 做的就是这个事情:

@Override
public void addFormatters(FormatterRegistry registry) {
	// 添加所有的 Converter
	for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
		registry.addConverter(converter);
	}
	
	// 添加所有的 GenericConverter
	for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {
		registry.addConverter(converter);
	}

	// 添加所有的 Formatter
	for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {
		registry.addFormatter(formatter);
	}
}

利用这个特性,只添加一个 Converter<String, Date> (Formatter也可以),可以在整个项目范围内将 String 类型的参数转换成 Date 类型。

比如,添加下面的配置。

import java.text.ParseException;
import java.util.Date;

import org.apache.commons.lang3.time.DateUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;

@Configuration
public class MyWebMvcConfigurationSupportSpringBoot {

	@Bean
	public Converter<String, Date> createConverter() {
		return new Converter<String, Date>(){

			@Override
			public Date convert(String source) {
				String[] dateFormatPatterns = {
						"yyyy-MM-dd HH:mm:ss", 
						"yyyy-MM-dd HH:mm", 
						"yyyy-MM-dd HH", 
						"yyyy-MM-dd"};
				try {
					return DateUtils.parseDate(source, dateFormatPatterns);
				} catch (ParseException ex) {
					throw new IllegalArgumentException("Could not parse date: " + ex.getMessage(), ex);
				}
			}
			
		};
	}
}

Spring MVC 专用的方式

Spring Boot 专用的方式 对等的也有 Spring MVC 专用的方式

利用 WebMvcConfigurationSupport 特性添加一个 Converter<String, Date> (Formatter也可以),可实现在整个项目范围内将 String 类型的参数转换成 Date 类型。

比如,添加下面的配置。

import java.text.ParseException;
import java.util.Date;

import org.apache.commons.lang3.time.DateUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class MyWebMvcConfigurerSupportSpring extends WebMvcConfigurationSupport {

	@Override
	protected void addFormatters(FormatterRegistry registry) {
		super.addFormatters(registry);
		
		registry.addConverter(new Converter<String, Date>(){

			@Override
			public Date convert(String source) {
				String[] dateFormatPatterns = {
						"yyyy-MM-dd HH:mm:ss", 
						"yyyy-MM-dd HH:mm", 
						"yyyy-MM-dd HH", 
						"yyyy-MM-dd"};
				try {
					return DateUtils.parseDate(source, dateFormatPatterns);
				} catch (ParseException ex) {
					throw new IllegalArgumentException("Could not parse date: " + ex.getMessage(), ex);
				}
			}
			
		});
	}
}

参考

https://blog.csdn.net/weixin_38229356/article/details/81228923
https://www.cnblogs.com/yy3b2007com/p/11757900.html
http://www.javaadu.online/?p=652
https://blog.csdn.net/weixin_38229356/article/details/81228923
https://blog.csdn.net/andy_zhang2007/article/details/87432541

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页