使用JSR303处理空值的SpringMVC控制器中的验证问题

使用JSR303处理空值的SpringMVC控制器中的验证问题,spring,validation,spring-mvc,dry,bean-validation,Spring,Validation,Spring Mvc,Dry,Bean Validation,SpringMVC有如下问题:我使用JSR303验证来确保bean的属性(参见下面的PasswordInfo)既不是空的也不是空的 我还检查了一条业务逻辑规则,即两个密码相同 问题是,如果其中一个字符串字段(currentPassword和newPassword)为空,它仍然由控制器传递到服务层,以检查业务逻辑规则,当然会引发IllegalArgumentException 下面是PasswordInfobean: @RooEquals @RooJavaBean public class Pas

SpringMVC有如下问题:我使用JSR303验证来确保bean的属性(参见下面的PasswordInfo)既不是空的也不是空的

我还检查了一条业务逻辑规则,即两个密码相同

问题是,如果其中一个字符串字段(currentPassword和newPassword)为空,它仍然由控制器传递到服务层,以检查业务逻辑规则,当然会引发IllegalArgumentException

下面是
PasswordInfo
bean:

@RooEquals
@RooJavaBean
public class PasswordInfo {

    @NotNull(groups = { ValidationGroups.PasswordModification.class })
    @NotEmpty(groups = { ValidationGroups.PasswordModification.class })
    private String currentPassword;

    @NotNull(groups = { ValidationGroups.PasswordModification.class, ValidationGroups.PasswordReset.class })
    @NotEmpty(groups = { ValidationGroups.PasswordModification.class, ValidationGroups.PasswordReset.class })       
    @Size(min = 6, groups = { ValidationGroups.PasswordModification.class, ValidationGroups.PasswordReset.class })
    private String newPassword;
...
以下是相关的控制器方法:

@RequestMapping(value = "/preference/password", method = RequestMethod.POST, produces = "text/html")
    public String modifyPassword(@ModelAttribute @Validated({ ValidationGroups.PasswordModification.class }) PasswordInfo passwordInfo,
            BindingResult bindingResult, Model model, @CurrentMember Member member) {
        if (!preferenceService.passwordsMatch(member.getPassword(), passwordInfo.getCurrentPassword())) {
            bindingResult.rejectValue("currentPassword", "controller.preference.wrong_current_password");
        }

        if (bindingResult.hasErrors()) {
            model.addAttribute("passwordInfo", passwordInfo);
            return "preference/password";
        }

        preferenceService.modifyPassword(member, passwordInfo.getNewPassword());
        return "redirect:/preference/password";
    }
@Override
    public boolean passwordsMatch(String encrypted, String plain) {
        if (encrypted == null || encrypted.isEmpty() || plain == null || plain.isEmpty()) {
            throw new IllegalArgumentException("One argument is null or empty");
        }
        return passwordEncoder.matches(plain, encrypted);
    }
以下是相关的服务层方法:

@RequestMapping(value = "/preference/password", method = RequestMethod.POST, produces = "text/html")
    public String modifyPassword(@ModelAttribute @Validated({ ValidationGroups.PasswordModification.class }) PasswordInfo passwordInfo,
            BindingResult bindingResult, Model model, @CurrentMember Member member) {
        if (!preferenceService.passwordsMatch(member.getPassword(), passwordInfo.getCurrentPassword())) {
            bindingResult.rejectValue("currentPassword", "controller.preference.wrong_current_password");
        }

        if (bindingResult.hasErrors()) {
            model.addAttribute("passwordInfo", passwordInfo);
            return "preference/password";
        }

        preferenceService.modifyPassword(member, passwordInfo.getNewPassword());
        return "redirect:/preference/password";
    }
@Override
    public boolean passwordsMatch(String encrypted, String plain) {
        if (encrypted == null || encrypted.isEmpty() || plain == null || plain.isEmpty()) {
            throw new IllegalArgumentException("One argument is null or empty");
        }
        return passwordEncoder.matches(plain, encrypted);
    }
我关心的是避免放置另一个bindingResults.hasErrors块,例如:

if (bindingResult.hasErrors()) {
        model.addAttribute("passwordInfo", passwordInfo);
        return "preference/password";
    }
…在业务逻辑检查之前,以避免重复我自己的操作


有人能为这个问题提供一个干净的解决方案吗?

我并没有遵循您的全部示例,老实说,我并不完全理解您的问题,但我想知道的一件事是,为什么您不将Bean验证也用于密码匹配约束?您可以为PasswordInfo编写自定义类约束。

这里的问题是,由于您提供了一个
BindingResult
作为参数,Spring MVC希望您在控制器方法中处理验证问题。它不会完全拒绝请求(即阻止对控制器方法的调用并引发异常)。您可能只需要重新构造方法以获得所需的结果:

@RequestMapping(value = "/preference/password", method = RequestMethod.POST, produces = "text/html")
public String modifyPassword(@ModelAttribute @Validated({ ValidationGroups.PasswordModification.class }) PasswordInfo passwordInfo,
                             BindingResult bindingResult, Model model, @CurrentMember Member member) {
    String view = "preference/password";
    if (!bindingResult.hasErrors()) {
        if (preferenceService.passwordsMatch(member.getPassword(), passwordInfo.getCurrentPassword())) {
            preferenceService.modifyPassword(member, passwordInfo.getNewPassword());
            view = "redirect:/preference/password";
        } else {
            bindingResult.rejectValue("currentPassword", "controller.preference.wrong_current_password");
        }
    }
    // NOTE: I have removed the call to model.addAttribute("passwordInfo", passwordInfo) because it should already exist in the model, no?
    return view;
}

哈代你好!事实上,这个问题与SpringMVC的关系比与JSR303的关系更大——尽管我在我的文章中添加了“BeanValidation”标记。基本上,我正在寻找一种干净的方法来避免在混合使用自定义验证和JSR303验证时重复
if(bindingResult.hasErrors())
块。你明白重点了吗?谢谢。实际上,您提供的代码是一个非常好的主意。我真的很想知道SpringMVC的最佳实践是什么:使用与您类似的代码(它工作得很好,但对嵌套ifs的读取和测试稍微有点困难),或者继续并放松服务层检查:保留“null”检查,但删除“isEmpty”检查…你对我的最后一条评论有什么看法?我不认为真的有一种“最佳”或“既定”的方式来构造你的逻辑…至少从Spring MVC的角度来看不是这样。我肯定会保留你的验证,因为这就是它的目的:验证。另外,关于我给出的例子,我仍然有使用单输入单输出范例的习惯。在我看来,它实际上创建了更干净的代码,但这在很大程度上取决于个人偏好(或团队偏好)。如果您对这个主题感兴趣,这里有一个关于它的讨论:很抱歉继续评论,但您可能还对Spring支持自定义验证程序移动密码匹配()感兴趣方法到其自己的303验证程序:=)