惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Project Zero
Project Zero
GbyAI
GbyAI
Y
Y Combinator Blog
Google DeepMind News
Google DeepMind News
小众软件
小众软件
The GitHub Blog
The GitHub Blog
阮一峰的网络日志
阮一峰的网络日志
J
Java Code Geeks
WordPress大学
WordPress大学
Microsoft Security Blog
Microsoft Security Blog
IT之家
IT之家
F
Fortinet All Blogs
博客园 - 【当耐特】
H
Hackread – Cybersecurity News, Data Breaches, AI and More
P
Proofpoint News Feed
Schneier on Security
Schneier on Security
C
Cybersecurity and Infrastructure Security Agency CISA
L
LINUX DO - 热门话题
Spread Privacy
Spread Privacy
O
OpenAI News
V
V2EX
博客园 - 三生石上(FineUI控件)
AI
AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Stack Overflow Blog
Stack Overflow Blog
P
Privacy International News Feed
Microsoft Azure Blog
Microsoft Azure Blog
Blog — PlanetScale
Blog — PlanetScale
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Jina AI
Jina AI
H
Help Net Security
L
LINUX DO - 最新话题
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
Tenable Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Cisco Talos Blog
Cisco Talos Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - 叶小钗
V
Vulnerabilities – Threatpost
C
Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
酷 壳 – CoolShell
酷 壳 – CoolShell
Hacker News: Ask HN
Hacker News: Ask HN
S
Security @ Cisco Blogs
S
Securelist
T
The Blog of Author Tim Ferriss
Apple Machine Learning Research
Apple Machine Learning Research
美团技术团队
雷峰网
雷峰网
V2EX - 技术
V2EX - 技术

博客园 - 黄洪波

安装openclaw 排查项目中依赖的mybatis 拦截器 使用calcite构造ddl建表语句 OpenWebUI单点登录之解决动态参数问题 IDEA自带的Maven 3.9.x无法刷新http nexus私服(转) windows docker安装rocketmq之踩坑记 分布式系统设计经典论文(转载) 训练自己的yolo-v11数据集(二) 训练自己的yolo-v11数据集(一) 本地使用pycharm进行yolo推理 2024年的云原生架构需要哪些技术栈 (转) yolo v11学习,入门篇 OpenWebUI单点登录之深坑 AI工具验证 解决Win10无法进入睡眠模式(转) idea常用插件 内网离线模式下激活JRebel java导入json数据至doris 将SpringBoot打包之后的jar设为守护进程
ModelAttribute 老革命遇上新问题
黄洪波 · 2026-01-30 · via 博客园 - 黄洪波

背景:
框架中的定义了一个统一参数,

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);
        }
    }