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

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 黄洪波

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