You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring WebFlux+Thymeleaf @ModelAttribute绑定Enum报构造函数缺失问题咨询

问题:响应式Thymeleaf开发中@ModelAttribute传递枚举参数抛出IllegalStateException异常

相关代码

控制器方法

@PostMapping("/lvl")
public Mono<String> getAllByLvl(@ModelAttribute Level level, Model model) {
    log.info(" level is: " +level);
    return Mono.just(model)
            .flatMap(m -> {
                m.addAttribute("skill",new Skill());
                m.addAttribute("skills",new ReactiveDataDriverContextVariable(service.findAllByLevel(level)));
                return Mono.just(TEMPLATE);
            });
}

Level枚举定义

@Getter
@NoArgsConstructor
public enum Level {
    A,
    B,
    C,
    D;
}

前端表单代码

<div>
    <h4>filter by lvl</h4>
    <form th:action="@{/skill/lvl}" th:object="${skill}" method="post">
        <select>
            <option value ="${skill.level}">A</option>
            <option value ="${skill.level}">B</option>
            <option value ="${skill.level}">C</option>
            <option value ="${skill.level}">D</option>
            <input type="submit" value="Submit"/>
        </select>
    </form>
</div>

Skill实体定义

@Data
@Table
@NoArgsConstructor
@AllArgsConstructor
public class Skill implements Persistable<UUID> {
    @Id
    private UUID id;
    @Column("skill_name")
    private String name;
    private Level level;
    private Priority priority;
    @Column("skill_group_name")
    private String skillGroupName;

    @Override
    public boolean isNew() {
        boolean result = Objects.isNull(id);
        this.id = result ? UUID.randomUUID() : this.id;
        return result;
    }
}

报错信息

java.lang.IllegalStateException: No primary or single public constructor found for class com.gmail.qwertygoog.roadmap.model.Level - and no default constructor found either
at org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:250) ~[spring-beans-5.3.7.jar:5.3.7]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:


解决方案

问题核心原因有两个:

  1. 错误对枚举类使用@NoArgsConstructor注解,且直接用@ModelAttribute绑定枚举类型参数,Spring尝试反射实例化枚举类失败(枚举类为固定实例的单例模式,不允许随意实例化)
  2. 前端表单select控件缺少name属性,option的value配置错误,导致参数无法正确传递到后端

以下为可落地的解决方法,按实现成本从低到高排序:

  • 方案1:修改参数绑定方式(推荐)

    1. 控制器参数将@ModelAttribute Level level改为@RequestParam Level level,Spring会自动将请求中的字符串参数转换为对应的枚举实例
    2. 前端select标签添加name="level"属性,修改option的value为对应枚举值:
    <select name="level">
        <option value="A">A</option>
        <option value="B">B</option>
        <option value="C">C</option>
        <option value="D">D</option>
        <input type="submit" value="Submit"/>
    </select>
    
    1. 删除Level枚举类上的@NoArgsConstructor注解,该注解不适用于枚举类型。
  • 方案2:沿用表单对象绑定逻辑
    你当前表单已经配置了th:object="${skill}",可以直接绑定Skill对象获取枚举参数:

    1. 控制器参数改为@ModelAttribute Skill skill,方法内直接调用skill.getLevel()获取枚举值
    2. 前端select标签添加th:field="*{level}"属性,自动完成参数绑定,可选优化为动态生成枚举选项:
    <select th:field="*{level}">
        <option th:each="levelItem : ${T(com.gmail.qwertygoog.roadmap.model.Level).values()}" 
                th:value="${levelItem}" 
                th:text="${levelItem}">
        </option>
        <input type="submit" value="Submit"/>
    </select>
    
    1. 删除Level枚举类上的@NoArgsConstructor注解。

内容的提问来源于stack exchange,提问作者tarmogoyf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.13 03:06:01