控制器或服务层中的 Spring MVC 验证?

Posted

技术标签:

【中文标题】控制器或服务层中的 Spring MVC 验证?【英文标题】:Validation in Spring MVC in Controllers or Service Layer? 【发布时间】:2014-02-19 21:13:12 【问题描述】:

在相当长的一段时间里,我试图弄清楚在 Spring MVC 应用程序中应该在哪里验证用户输入。在许多在线博客和教程中,我基本上读到控制器应该验证用户输入,如果无效,则通过显示包含错误消息的页面来响应用户。然而,我目前对 Spring 和 Spring MVC 分层系统的理解是,Controller 是应用程序逻辑(服务层)和“Web 世界”之间的唯一浅层接口,允许从 Web 使用服务层。另外,据我所知,Spring MVC 确实只提供了在 Controller 中进行验证的合理工具。

如果现在验证发生在控制器中,如果稍后我想从“网络世界”中解开应用程序逻辑,则必须在新环境中重新实现验证逻辑(例如使用 Swing 的桌面应用程序)。在我看来,决定哪些操作在域对象上“有效”以及这些对象可能具有哪些“有效”状态的能力是服务层的核心部分,而不是应用程序其他部分的关注点(例如控制器)。

在这种情况下,为什么将输入验证逻辑放在控制器层而不是服务层是“好的做法”?

【问题讨论】:

jeejava.com/spring-service-layer-bean-validation/ 【参考方案1】:

在我们之前的一个项目中,我们的表单非常庞大,逻辑非常复杂,这意味着需要大量验证代码。所以我们使用了第三种解决方案。对于每个控制器,我们自动装配了一个辅助类。 示例:

myview <-> MyController <- MyService <- MyDAO
                 ^
                 |
               MyHelper

Controllers 处理视图解析。Services 处理从 dto-s 到视图模型对象的映射,反之亦然,DAO-s 处理数据库事务,Helpers 处理其他一切,包括验证。

如果现在有人想将前端从 web 更改为其他东西,那会容易得多,同时,我们不会过度膨胀服务实现类。

【讨论】:

【参考方案2】:

一种常见的方法是在两个地方都进行验证。但是如果你在谈论@Valid,根据我的经验,最好放在控制器级别。

这还取决于我们谈论的是哪种验证逻辑。假设你有一个 bean:

@Data
public class MyBean 
    @NotNull private UUID someId;
    @NotEmpty private String someName; 

在控制器级别使用@Valid 注释这个bean 是有意义的,因此它甚至不会到达服务。将@Valid 放在服务方法上没有任何好处,因为您为什么要进一步传播它,而您可以立即在控制器中决定它是否有效。

还有第二种类型的验证:业务逻辑验证。假设对于同一个 bean,someId 属性是一个 timeUUid,并且它的时间戳最多需要在某个事件发生后 2 天,否则,该 bean 应该被服务丢弃。

这似乎是一个业务逻辑验证案例,因为仅查看 bean,您将无法验证它,除非您对其应用一些逻辑。

由于这两种验证方法实际上验证的是不同的东西,很明显,您的每个 MVC 组件(模型、视图和控制器)都进行自己的验证,并且在不引入相互依赖的情况下验证的内容应该是合理的组件。

至于向用户显示错误,是的,Errors 对象确实旨在用于控制器级别的 bean 验证,但是您可以设计一些过滤器来捕获任何级别的异常,然后将其格式化为用户。有很多方法,我不确定 Spring 是否规定任何一个都比另一个更好

根据不同的解析机制(例如,jstl 或 jackson 或其他),您可能会倾向于以不同的方式处理验证。例如,传统的jstl view resolver 可以很好地与使用错误的装置一起工作,而jackson resolver 可能会更好地结合@ResponseBody 和一些过滤器来捕获错误并将它们放入预定义的错误部分响应对象。

【讨论】:

@xSNRG 没有理由在两个验证范围之间进行同步。控制器层应该简要检查它可以验证的任何 bean 属性,如果一切看起来都有效,则传递给服务。然后服务应该进行自己的业务验证并将其传递给模型(它应该有自己的域约束和验证事物)。如果您不将这些层紧密耦合,则不需要在它们之间进行任何同步,而只需单独维护每个层。 您的回答并不完全清楚,但是您是否建议不应将 javax.validation 用于业务逻辑验证?如果是这样,您可以详细说明一下,根据我目前的理解,您似乎可以使用两个单独的自定义验证器来分离关注点,如果您正在执行的两种类型的验证是如此不同以至于它们应该是分开的,但相似之处在于它们验证了相同的对象/类。 @CopyandPaste 我在回答中提到了两种类型:“原始正确性”验证类型:s 数据实际上是有意义的):例如,是否填写了必填字段,是整数正数等。然后有业务验证,这可能比简单地检查几个属性是否为空要复杂得多,例如,您是否有订单的能力,这甚至可能涉及数据库调用,您可能会与“原始”分开验证并放入验证服务或类似的东西。 javax.* 比其他类型更适合“原始”,但这不是 100% 的规则。

以上是关于控制器或服务层中的 Spring MVC 验证?的主要内容,如果未能解决你的问题,请参考以下文章

服务层中的Spring @Validated

使用 Spring MVC 和 Knockout JS 进行服务器端验证

服务层中的 JsonResult

根据 DTO、实体模型或其他东西验证服务层中的数据?

服务或控制器中的 Spring DTO 验证?

混合身份验证 - 基于 Spring MVC 会话 + 基于 JWT 令牌