背景:
框架中的定义了一个统一参数,
框架中的定义了一个统一参数,
public class OperationParam { String operation; ... Map<String, Object> arg; }
这个结构用于接收常规的RequestBody没有任何问题,但是我的一个新场景是需要上传文件,于是定义了一个新类继承他
public class OperationWithFileParam extends OperationParam{ /** * 文件参数 */ @NonNull MultipartFile file; }
controller如下:
@PostMapping(value = "/invoke", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public SingleResponse<Object> invokeMethodWithFile(@Validated @ModelAttribute OperationWithFileParam param) { return SingleResponse.success(service.invoke(param, false)); }
OK,前端正常传入form-data参数,这时候问题来了:
报错:无法将String转换为一个map
即便是在OperationWithFileParam 中写了
示例:
public void setArg(String arg){
setArg(JsonUtils.parseObject(arg, Map.class));
}
也不行,还没执行到这里就报错了
为什么直接使用 arg 参数会报错:
- 表单提交的
arg参数是 String 类型(JSON 字符串) - Spring 发现 Bean 有
setArg(Map)方法,认为arg属性应该是Map类型 - Spring 尝试将 String 转换为 Map,但没有合适的转换器
- 于是在
ModelAttributeMethodProcessor.resolveArgument()时抛出typeMismatch异常 - 根本没有机会进入你重载的
setArg(Object)方法
有两种解决办法:
方案1.快速止血,定义一个新参数,如String argJson,
String argJson; public void setArgJson(String argJson) { if (StringUtils.hasText(argJson)) { Map<String, Object> argMap = JsonUtils.parseObject(argJson, new TypeReference<Map<String, Object>>() { }); setArg(argMap); } }
为什么 argJson 可以工作:
argJson对应的是String类型的字段和 setter- Spring 直接将表单参数的 String 值绑定到
argJson字段 - 然后你的自定义
setArgJson(String)方法内部解析 JSON 并调用父类的setArg(Map)
这个方案虽然可以工作,但是始终不是那么的优雅。
方案2:为ModelAttribute指定一个InitBinder
我用的是SpringBoot3,有两种指定方案,我选择了老的那种,看起来简单。
@PostMapping(value = "/invoke", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public SingleResponse<Object> invokeMethodWithFile(@Validated @ModelAttribute("operationInvokeFile") OperationWithFileParam param) { return SingleResponse.success(service.invoke(param, false)); } /** * 初始化参数转换器 * 看起来简洁明了 * * @param binder * 参数绑定器 */ @InitBinder("operationInvokeFile") public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(Map.class, "arg", new PropertyEditorSupport() { @Override public void setAsText(String text) throws IllegalArgumentException { if (StringUtils.hasText(text)) { setValue(JsonUtils.parseObject(text, new TypeReference<Map<String, Object>>() {})); } } }); } /** * 初始化参数转换器 * springboot3 支持的做法,看起来稍微复杂一些 * * @param binder * 参数绑定器 */ @InitBinder("operationInvokeFile") public void initBinder(WebDataBinder binder) { binder.addCustomFormatter(new ArgJsonFormatter(), "arg"); } private static class ArgJsonFormatter implements Formatter<Map<String, Object>> { @Override public Map<String, Object> parse(String text, Locale locale) { if (!StringUtils.hasText(text)) { return Collections.emptyMap(); } return JsonUtils.parseObject(text, new TypeReference<Map<String, Object>>() {}); } @Override public String print(Map<String, Object> object, Locale locale) { return JsonUtils.toJsonString(object); } }
浙公网安备 33010602011771号