Java 使用SpringMVC向用户显示服务级别验证错误的良好实践

Java 使用SpringMVC向用户显示服务级别验证错误的良好实践,java,spring,validation,spring-mvc,Java,Spring,Validation,Spring Mvc,在使用SpringMVC验证器执行“浅层”用户输入验证之后,是否有一些好的实践可以使用SpringMVC显示服务层验证错误?例如,拥有以下代码: @Autowired private UserService userService; @RequestMapping(value = "user/new", method = RequestMethod.POST) public String createNewUser(@ModelAttribute("userForm") UserForm us

在使用SpringMVC验证器执行“浅层”用户输入验证之后,是否有一些好的实践可以使用SpringMVC显示服务层验证错误?例如,拥有以下代码:

@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) 向用户显示这些验证错误


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

选择通常是异常与错误代码(或响应代码),但最佳实践,至少是布洛赫的,是仅在异常情况下使用异常,这会使它们在这种情况下失去资格,因为用户选择现有用户名并非闻所未闻

服务调用中的问题是,您假设
createUser
是一个命令,没有返回值。你应该把它想象成“尝试创建一个用户,并给我一个结果”。届时,这一结果可能会有所改变

  • 整数代码(可怕的想法)
  • 共享结果枚举中的常量(由于可维护性,这仍然是个坏主意)
  • 一个更具体的常量,如
    UserOperationResult
    enum(最好是在创建新用户和尝试修改用户时返回
    USER\u已存在的值)
  • 一个完全自定义于此调用的
    UserCreationResult
    对象(这不是一个好主意,因为您会得到大量这样的调用)
  • 一个
    Result
    UserOperationResult
    包装器对象,当没有返回值时,它组合了一个响应代码常量(
    ResultCode
    UserOperationResultCode
    )和一个返回值
    T
    ,或一个通配符
    。。。注意切入点之类的东西(不要期待包装器)

未经检查的异常的美妙之处在于它们避免了所有这些废话,但它们带来了自己的一系列问题。我个人会坚持最后一个选项,并且在过去有过不错的运气。

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

因此,您可以稍微修改控制器方法:

@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";
} 
然后,您的
userserviceinpl
将检查重复的用户本身-例如:

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相关的代码。

您会让控制器处理
结果的处理方式,还是将其传递给视图并让其处理?控制器将决定如何处理服务调用结果,更新模型,视图将呈现所需的任何内容。有几个原因;了解服务调用枚举的视图可能会混合关注点,控制器可能需要进行多个服务调用并合并结果,控制器可能需要对结果负载的子集进行操作,等等。非常感谢。这很有帮助。我已经有了一个类似的想法,我只希望有一些神奇的春天的方式来做到这一点。尽管如此,这个解决方案是向前迈进的,可能会被其他阅读我的代码的人立即理解。您可以在控制器上使用有效或经过验证的注释,但一旦进入服务层,Spring就不会妨碍您防止耦合。也就是说,您仍然可以使用@Valid的方面(请参阅Hibernate Validator 5中的方法验证支持)。您还可以为自定义的AvailableUsername注释创建自己的ConstraintValidator,从控制器自动检查它,如果违反了,则从服务层抛出异常,但这也会变得一团糟;就我个人而言,我不喜欢将可变簿记对象推到调用层次结构下。我还想知道
错误
是否会随着
ConstraintViolation
而消失,因为后者已经作为Bean验证API的一部分进行了标准化。如果将来我想将服务层与Spring MVC以外的其他表示层技术(例如JSF)一起使用,该怎么办?我的意思是,当服务层在error对象中生成如下路径时,它会使转换变得可怕:errors.rejectValue(“someList[“+index+”].someValue”、“someValue.isEmpty”、“Value不应为空”)。您必须首先手动解析“someList[5].someValue”之类的字符串,然后才能将正确的错误消息传递给JSF视图。恐怕我对JSF及其如何使用Spring的验证框架进行注释还不够熟悉。答案有正反两方面。主要的优点是简单。主要问题是与服务层中的验证框架的耦合。如果您计划切换表示层,那么返回某种类型的
结果
对您来说可能是一个更好的选择-正如Emerson在回答中所建议的那样。@Will Keeling:谢谢。我将发布我的解决方案以分享我的讨论结果。jeejava.com/spring-service-layer-bea