在 Spring Boot 应用程序中使用 @Valid 和 BindingResult 时的表单输入验证问题

Posted

技术标签:

【中文标题】在 Spring Boot 应用程序中使用 @Valid 和 BindingResult 时的表单输入验证问题【英文标题】:Problem with form input validation in using @Valid and BindingResult in a Spring Boot App 【发布时间】:2019-09-07 11:01:55 【问题描述】:

我正在尝试向我的表单添加代码端验证。我基于本教程:https://www.javacodegeeks.com/2017/10/validation-thymeleaf-spring.html - 但不费吹灰之力。

我有一个实体 InvoiceData:

@Data
@Document
@NoArgsConstructor
public class InvoiceData 

    @Id private String id;
    private ContractorData data;
    @NotNull
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date receptionDate;
    @NotNull
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date orderDate;
    @NotNull
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date invoiceIssueDate;
    @NotNull
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @NotNull
    private Date contractDate;
    @NotBlank
    private String invoiceNumber;
    private String additionalCosts;
    private String contractorComment;
    @NotEmpty
    private List<InvoiceTask> invoiceTasks = new ArrayList<>();

还有一个控制器方法:

@RequestMapping(value = "/addinvoice/contractorId", method = RequestMethod.POST, produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public String addInvoice(@PathVariable("contractorId") String contractorId, @ModelAttribute @Valid InvoiceData data, Model model, BindingResult result, RedirectAttributes attr, HttpSession session) 
        if (result.hasErrors()) 
            System.out.println("BINDING RESULT ERROR");
            attr.addFlashAttribute("org.springframework.validation.BindingResult.data", result);
            attr.addFlashAttribute("register", result);
            return "redirect:/add";
         else 
            Contractor contractor = contractorRepository.findById(contractorId).get();
            data.setData(contractor.getContractorData());
            if (contractor.getInvoices() == null) 
                contractor.setInvoices(new ArrayList<InvoiceData>());
            
            contractor.getInvoices().add(data);
            invoiceDataRepository.save(data);
            contractorRepository.save(contractor);
            model.addAttribute("contractor", contractor);
            return "index";
        
    

为了清晰起见,还有一小块百里香叶(所有其他字段看起来都像这个)

<form action="#" th:action="@addinvoice/id(id=$contractorid)" th:object="$invoicedata" method="post">
    <ul class="form-style-1">
        <li>
            <label>Reception date<span class="required">*</span></label>
            <input type="date" th:field="*receptionDate" id="receptionDate">
        </li>

问题是当我尝试发送无效表单时,我没有被重定向到/add,但我收到一个错误页面:

出现意外错误(类型=错误请求,状态=400)。 对象='invoiceData' 的验证失败。错误计数:6

还有堆栈跟踪(为了清楚起见,仅来自一个字段):

字段“invoiceIssueDate”的对象“invoiceData”中的字段错误:拒绝值 [null];代码 [NotNull.invoiceData.invoiceIssueDate,NotNull.invoiceIssueDate,NotNull.java.util.Date,NotNull];参数 [org.springframework.context.support.DefaultMessageSourceResolvable:代码 [invoiceData.invoiceIssueDate,invoiceIssueDate];论据 [];默认消息 [invoiceIssueDate]];默认消息[不能为空]

所以我认为这是我可以从验证器中得到的行为之一。

但是有一点,当我在控制器中设置断点时,在 if 语句开始的方法的开头,并且我发送了一个无效的表单,调试器永远不会停在那里,所以看起来这个代码永远无法到达...

但是当我发送正确填写的表格时 - 一切正常,代码有效,数据发送到数据库等......

我的问题是:这是验证器的正常行为吗?当表单无效时,我该怎么做才能让代码运行,以便我可以获取 BindingResult 并向用户显示一些错误输出?

【问题讨论】:

验证在其他任何事情之前发生(除非验证正常,否则它不会访问您的代码)。尝试打破验证器 (javax.validation.Validator) 而不是你的代码。 你的问题是什么?... @Blazerg 很抱歉,我已经发送了问题,但没有检查我没有写出我的问题是什么。现在更新了。 你的BindingResult 放错了地方,它必须直接跟在@ModelAttribute 带注释的参数之后。你的在 Model 属性之后。切换属性的顺序。 AFAIK,控件应该通过if 块并且调试器应该暂停。可能您没有放置一个 checked 无效的表单字段。 【参考方案1】:

您需要将 BindingResult 参数移动到具有 @Valid 注释的参数旁边。

@RequestMapping(value = "/addinvoice/contractorId", method = RequestMethod.POST, produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String addInvoice(@PathVariable("contractorId") String contractorId, @ModelAttribute @Valid InvoiceData data, BindingResult result, Model model , RedirectAttributes attr, HttpSession session) 
    if (result.hasErrors()) 
        System.out.println("BINDING RESULT ERROR");
        attr.addFlashAttribute("org.springframework.validation.BindingResult.data", result);
        attr.addFlashAttribute("register", result);
        return "redirect:/add";
     else 
        Contractor contractor = contractorRepository.findById(contractorId).get();
        data.setData(contractor.getContractorData());
        if (contractor.getInvoices() == null) 
            contractor.setInvoices(new ArrayList<InvoiceData>());
        
        contractor.getInvoices().add(data);
        invoiceDataRepository.save(data);
        contractorRepository.save(contractor);
        model.addAttribute("contractor", contractor);
        return "index";
    

现在 BindingResult 变量将附加到 InvoiceData 变量。此外,如果您要验证 API 中的多个参数,则需要在所有这些参数旁边声明其对应的 BindingResult 变量。

【讨论】:

以上是关于在 Spring Boot 应用程序中使用 @Valid 和 BindingResult 时的表单输入验证问题的主要内容,如果未能解决你的问题,请参考以下文章

如何设置Spring Boot中@RequestBody反序列化实体的默认值

带有模型和百里香叶的 Spring Boot Ajax 发布表单提交

spring boot 手动value和自动注入配置的区别

Spring Boot 官方文档学习入门及使用

spring-boot主要注解

Spring Boot Questions- Part 1