为啥 Grails 建议将操作作为方法的控制器使用单例范围?

Posted

技术标签:

【中文标题】为啥 Grails 建议将操作作为方法的控制器使用单例范围?【英文标题】:Why does Grails recommend singleton scope for controllers with actions as methods?为什么 Grails 建议将操作作为方法的控制器使用单例范围? 【发布时间】:2015-06-23 07:54:00 【问题描述】:

我知道早期版本的 Grails 使用原型作用域作为控制器,因为当时动作都是闭包。

我知道当前版本的文档建议将单例范围的控制器用于将方法用作操作的控制器。

从下面的帖子看来,方法和单例范围似乎更可取或推荐,但不清楚原因。 ttp://grails.1312388.n4.nabble.com/Default-scope-for-controllers-doc-td4657986.html

我们有一个大型项目,它使用原型作用域控制器和操作作为方法。更改为推荐的控制器范围涉及风险和重新测试,以及从现有控制器中删除任何非单例友好状态。

我想知道为什么 Grails 推荐单例范围将方法作为动作控制器?是因为它更常见,更类似于 Spring MVC,并且避免了混淆,还是有提高性能的机会,还是什么? 如果我切换到单例控制器,我会得到什么?如果我不进行切换,成本是多少?

【问题讨论】:

这是一个更适合groups.google.com/forum/#!forum/grails-dev-discuss 的讨论。简短的回答是,除非您正在做其他次优的事情,否则您不需要为每个请求创建一个新的控制器实例。单例控制器几乎总是正确的。如需讨论,请发布到邮件列表。我将此作为评论而不是答案,因为这对于 *** 来说并不是一个好问题。 杰夫,感谢您的评论和简短的回答。我按照你的建议发表了讨论。惊讶于您觉得 *** 问题不好,这似乎很适合我,因为文档建议进行更改但没有说明更改的理由,并且在 Internet 上的其他地方没有对该问题的可靠答案,我能够去寻找。 “很惊讶你觉得 *** 问题不好” - 它不是一个好的 SO 问题的部分原因是它提出了 4 个单独的问题。它们是相关但独立的问题。围绕这一点的影响并非微不足道,对于大多数人来说,需要进行讨论才能真正解决所有问题。我看到你已经接受了伯特给出的答案,所以我很高兴你得到了你需要的东西。 杰夫,好的,关于 4 个问题的要点。我想我想留下这个问题,因为 Burt 的信息非常详尽,而且我认为,对于从早期 Grails 版本开始的任何其他大型项目,现在必须对重构为单例控制器进行成本效益分析。再次感谢你们两位的帮助。非常感谢。 “对于任何其他从早期 Grails 版本开始的大型项目来说都很重要,现在必须对重构为单例控制器进行成本效益分析”——重构并没有真正的新好处。现在的好处/缺点和以前一样(大部分情况下)。任何可以证明在以前版本的 Grails 中利用有状态控制器的人都可能会使用相同的论据来证明在最近版本的 Grails 中这样做是合理的。我认为这些理由在很大程度上是虚假的,但它们不会改变。 【参考方案1】:

我在 Rails 上工作不多,但是(至少在我玩过的版本中,现在情况可能有所不同)控制器是模型,包含要由视图呈现的数据。在请求处理期间,您将值存储在控制器实例字段中,然后再将处理交给查看渲染器。因此,为每个请求创建一个新的控制器实例是有意义的,因此它们是不同的。

Grails 受到 Rails 的启发,并使用了它的几个约定,最著名的是约定优于配置。但是使用控制器作为模型的能力也被添加为一个特性,虽然它没有很好的文档记录,我怀疑很多人都使用过它。

当使用 GSP 呈现响应时,控制器操作的典型工作方式(与转发或重定向相反,或直接在控制器中呈现,例如使用 render foo as JSON)是返回一个带有一个或多个键的 Map/操作中的值对,并且经常省略 return 关键字,因为它在 Groovy 中是可选的:

class MyController 
   def someAction() 
      def theUser = ...
      def theOtherObject = ...
      [user: theUser, other: theOtherObject]
   

这里的模型映射有两个条目,一个由user 键入,另一个由other 键入,这些将是 GSP 中用于访问数据的变量名称。

但大多数人不知道的是,您也可以这样做:

class MyController 

   def user
   def other

   def someAction() 
      user = ...
      other = ...
   

在这种情况下,操作不会返回模型映射,因此 Grails 将从控制器类的所有属性中填充模型,在这种情况下,相同的 GSP 将适用于两种方法,因为第二个方法中的变量名方法与第一个中的映射键相同。

在 2.0 中添加了使控制器单例的选项(在重命名为 2.0 之前的技术上是 1.4,请参阅this JIRA issue),除了保留对闭包的支持外,我们还添加了对方法作为操作的支持。最初的假设是使用闭包会启用一些有趣的功能,但这从未发生过。使用方法更自然,因为您可以在子类中覆盖它们,这与只是类范围字段的闭包不同。

作为 2.0 返工的一部分,我们删除了受 Rails 启发的功能,假设因为它基本上没有记录,对使用该功能的少数奇怪应用程序的影响不会很大。我不记得有人抱怨过失去该功能。

虽然控制器类通常很容易被垃圾回收,并且为每个请求创建一个实例不会产生太大影响,但控制器中很少需要每个请求状态,因此单例通常更有意义。保留默认原型范围是为了向后兼容,但使用 Config.groovy 属性可以轻松更改默认值(create-app 脚本生成的文件会执行此操作)。

虽然每个请求都会得到一个新的请求和响应,并且如果使用会话,每个用户都会有自己的,但这些不是控制器的实例字段。它们看起来是因为我们可以在操作内部访问requestresponsesessionparams 等,但这些实际上是getRequest()getResponse()、@ 的属性访问形式987654335@ 和 getParams() 在编译期间通过 AST 转换混合到所有控制器中的方法。这些方法不是作为类字段而是通过 ThreadLocals 访问它们的对象,因此存在每个请求的状态,但它不存储在控制器实例中。

我不记得在比较使用方法和闭包时是否有很多基准测试方法,但 Lari Hotari 可能做了一些。如果有差异,它可能并不显着。您可以在您自己的应用程序中测试这一点,只需转换一个或几个控制器并在测试之前和之后进行。如果这两种方法之间的性能、缩放和内存差异不显着,那么您可能可以安全地使用原型分数和/或闭包。如果存在差异并且您的控制器中没有实例字段,那么转换为单例和方法可能是值得的。

如果您确实有实例字段,它们可能可以转换为请求属性 - request.foo = 42request.setAttribute('foo', 42) 的元类快捷方式,因此您可以在那里安全地存储每个请求的数据。

【讨论】:

Burt,非常感谢您如此详细的回复。现在我觉得我知道整个故事了。

以上是关于为啥 Grails 建议将操作作为方法的控制器使用单例范围?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Grails 使用 Ivy 作为构建和依赖管理器? [关闭]

为啥 Grails 要求我在控制器中使用 `def` 而不是 `void`?

让 Grails 控制器更干燥?

Grails 重定向控制器从 https 切换到 http。为啥?

Grails 检查特定控制器操作的角色访问

为啥使用 JBoss 进行 Grails 部署?