• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
黄洪波写点东西的地方
博客园    首页    新随笔    联系   管理    订阅  订阅
ModelAttribute 老革命遇上新问题
背景:
框架中的定义了一个统一参数,
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);
        }
    }

 

posted on 2026-01-30 09:03  黄洪波  阅读(0)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3