使用Spring MVC向用户呈现服务级别验证错误的良好实践

时间:2014-01-28 17:44:06

标签: java spring validation spring-mvc

在使用Spring MVC验证器执行“浅”用户输入验证后,是否有一些使用Spring MVC呈现服务层验证错误的良好实践?例如,拥有以下代码:

@Autowired
private UserService userService;

@RequestMapping(value = "user/new", method = RequestMethod.POST)
public String createNewUser(@ModelAttribute("userForm") UserForm userForm, BindingResult result, Model model){
    UserFormValidator validator = new UserFormValidator(); //extending org.springframework.validation.Validator
    validator.validate(userForm, result);

    if(result.hasErrors()){
        model.addAttribute("userForm", userForm);
        return "user/new";
    }

    // here, for example, the user already might exist
    userService.createUser(userForm.getName(), userForm.getPassword());

    return "redirect:user/home";
} 

虽然将此代码作为示例似乎微不足道,但在服务层验证是一项复杂的任务时,对我来说似乎是一个微妙的故事。尽管是一个荒谬的场景,UserService可能会创建一个用户列表,如果其中一个已经存在,那么视图层必须以某种方式通知其中哪些无效(例如确实存在)。

我正在寻找一个如何设计一段代码的良好实践,这使得

成为可能

1)处理具有复杂数据作为输入的服务层的验证错误,并且

2)向用户提供这些验证错误

尽可能简单。有什么建议吗?

2 个答案:

答案 0 :(得分:9)

选择通常是异常与错误代码(或响应代码),但最佳做法,至少是Bloch,只是在特殊情况下使用例外情况,这会在这种情况下取​​消资格,因为用户选择现有的用户名并非闻所未闻。

您的服务调用中的问题是您认为createUser是命令性命令,没有返回值。您应该将其视为“尝试创建用户,并给我一个结果”。那么结果可能是

  • 整数代码(可怕的想法)
  • 来自共享结果枚举的常量(由于可维护性而仍然是个坏主意)
  • 来自更具体的内容的常量,例如UserOperationResult枚举(更好的主意,因为您可能希望在创建新用户和尝试修改用户时返回USER_ALREADY_EXISTS
  • 一个UserCreationResult对象,它完全适合这个调用(不是一个好主意,因为你会爆炸这些)
  • 一个Result<T>UserOperationResult<T>包装器对象,它将响应代码常量(分别为ResultCodeUserOperationResultCode)和返回值T或通配符组合在一起?当没有返回值时......只需注意不期望包装的切入点等)

未经检查的例外之处在于它们避免了所有这些废话,但它们带有一系列问题。我个人坚持最后一个选择,并且过去曾经有过不错的运气。

答案 1 :(得分:5)

抛出异常/返回错误代码的替代方法是将Errors传递给userService.createUser()。然后可以在服务级别执行重复的用户名检查 - 并附加到Errors的任何错误。这样可以确保所有错误(浅层和更复杂)都可以被收集起来并同时呈现给视图。

所以你可以稍微改写你的控制器方法:

@RequestMapping(value = "user/new", method = RequestMethod.POST)
public String createNewUser(@ModelAttribute("userForm") UserForm userForm, BindingResult result, Model model){
    UserFormValidator validator = new UserFormValidator();
    validator.validate(userForm, result);

    // Pass the BindingResult (which extends Errors) to the service layer call
    userService.createUser(userForm.getName(), userForm.getPassword(), result);

    if(result.hasErrors()){
        model.addAttribute("userForm", userForm);
        return "user/new";
    }

    return "redirect:user/home";
} 

然后您的UserServiceImpl会检查重复的用户本身 - 例如:

public void createUser(String name, String password, Errors errors) {
    // Check for a duplicate user
    if (userDao.findByName(name) != null) {
        errors.rejectValue("name", "error.duplicate", new String[] {name}, null);
    }

    // Create the user if no duplicate found
    if (!errors.hasErrors()) {
        userDao.createUser(name, password);
    }
}

Errors类是Spring验证框架的一部分 - 所以尽管存在对Spring的依赖,但服务层不会依赖任何与Web相关的代码。