在使用springboot或者说在使用springmvc时,很多人都会遇到请求到达controller时,如何将Request中的数据传递到对应的方法上,但是我想很多人都会遇到一些关于参数转换的问题,主要包括两个方面:
1、请求中字符串类型的参数如何转换成controller对应方法的参数
2、请求数据格式与参数填充问题
怎么理解上面提出的两点,举个例子,当我们请求为字符串类型时,而参数是java.util.Date,那么能够正常解析并响应吗?这就是第一点的问题;如果我们需要获取字符串数字类型参数,请求中应该如何定义请求参数,这就是第二种问题。下面来分析下具体示例:
首先搭建一个springboot的项目,这么没什么说的,我们先不做任何配置,只用写一个controller,然后启动项目:
@RestController
@RequestMapping
public class TestControler {
@GetMapping
public Object test(@RequestParam("date") Date date){
return date;
}
}
通过postman发送请求:
GET localhost:8080?date=2019-07-26
不出任何意外的出现错误:
{
"timestamp": 1564147155806,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.method.annotation.MethodArgumentTypeMismatchException",
"message": "Failed to convert value of type 'java.lang.String' to required type 'java.util.Date'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam java.util.Date] for value '2019-07-26'; nested exception is java.lang.IllegalArgumentException",
"path": "/"
}
一般我们会怎么解决:
1、通过 @InitBinder
@InitBinder public void initBinding(WebDataBinder dataBinder){ dataBinder.registerCustomEditor(Date.class, new DatePropertyEditor() ); }
@Slf4j public class DatePropertyEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date date = null; try { date = sdf.parse(text); } catch (ParseException e) { log.error("",e); } super.setValue(date); } }
2、通过 @DateTimeFormat(pattern = "yyyy-MM-dd")
@GetMapping public Object test(@RequestParam("date") @DateTimeFormat(pattern = "yyyy-MM-dd") Date date){ return date; }
3、实现接口Converter<String,Date>
@Slf4j @Component public class StringDateConverter implements Converter<String,Date> { @Override public Date convert(String text) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); try { return sdf.parse(text); } catch (ParseException e) { log.error("",e); } return null; } }
4、实现接口ConverterFactory<String,Date>
@Slf4j public class StringToDateConverterFactory implements ConverterFactory<String,Date> { @Override public <T extends Date> Converter<String,T> getConverter(Class<T> clazz) { return new StringToDate<T>(clazz); } private static class StringToDate<T extends Date> implements Converter<String,T> { private Class<T> targetType; public StringToDate(Class<T> targetType) { this.targetType = targetType; } @Override public T convert(String source) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); try { Date date = sdf.parse(source); return (T) date; } catch (ParseException e) { log.error("",e); } return null; } } }
注册到容器:
@Configuration public class WebMvcConfiguration extends WebMvcConfigurerAdapter { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverterFactory(new StringToDateConverterFactory()); } }
5、实现接口 GenericConverter
这种方法比较复杂,没有研究过。
6、HandlerMethodArgumentResolver
通过HandlerMethodArgumentResolver我们可以将请求中的数据封装成指定类型,在对请求组装成目标类型数据时,该方法是非常有用的,同时在项目中使用概率也是比较高。
看一下示例,将前台提交的数据组装成指定类型Request
@Data
public class Request {
private String id;
private String method;
private String date;
private String name;
private String description;
}
public class RequestDataHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestData.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Class<?> paramClazz = parameter.getParameterType();
Object param = BeanUtils.instantiate(paramClazz);
Iterator<String> reqParams = webRequest.getParameterNames();
Map<String,String> paramMap = new HashMap<>();
while (reqParams.hasNext()){
String propKey = reqParams.next();
paramMap.put(propKey,webRequest.getParameter(propKey));
}
BeanInfo beanInfo = Introspector.getBeanInfo(paramClazz);
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for(PropertyDescriptor pd : pds){
String prop = pd.getName();
Class<?> propType = pd.getPropertyType();
Method setMethod = pd.getWriteMethod();
if(setMethod!=null && propType == String.class)
setMethod.invoke(param,paramMap.get(prop));
}
return param;
}
}
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new RequestDataHandlerMethodArgumentResolver());
}
}
测试:
@PostMapping("/request")
public Object request(@RequestData Request request){
return request;
}
这样我们请求中的数据则会自动转换成Request对象了。
总结:需要特别说明一点的是,如果通过@EnableWebMvc时,通过Converter相关接口时,需要通过方法4中的方式注册到容器,否则将不生效,具体原因还需要对比两种配置的区别。
参考:https://blog.youkuaiyun.com/fsp88927/article/details/37692215
下面看下关于参数转换一些问题 ,一般如果接受参数类型为数组,比如string[] array,比如下面这样的:
@PostMapping("/array") public Object array(@RequestBody String[] arr){ return arr; }
我们知道,前端在向后台发送请求,常用的一般有两种:application/x-www-form-urlencoded , application/json
针对第一种,我们只需要将参数构建成 arr=1&arr=2....这样的形式即可,比如在已数组作为参数时,一般浏览器都会转换成arr[]=1 arr[]=2...其实这个时候通过@RequestParam("arr[]")就可以了,毕竟js对象不能有相同的key。
如果是json的呢,那么没有办法,只能将数组放到对象中:
@Data @AllArgsConstructor @NoArgsConstructor public class Record { private String id; // @JsonFormat @DateTimeFormat(pattern = "yyyy-MM-dd") private Date date; private String[] arr; }
@PostMapping("/record") public Object record(@RequestBody Record record){ return record; }