【问题标题】:Scala (Play 2.4.x) How to call a class with @inject() annotationScala (Play 2.4.x) 如何使用@inject() 注解调用类
【发布时间】:2015-09-29 08:35:53
【问题描述】:

我正在查看 play-mailer 中的鳞状代码示例:https://github.com/playframework/play-mailer

基本上是这样的:

class MyComponent @Inject() (mailerClient: MailerClient) {
   ...
}

足够简单,编译时不兼容

然后我尝试“调用”它,但似乎没有一种方法可以满足编译器或获取 mailerClient 的工作实例。

object AnObject {
  val mailer = new MyComponent
  def sendEmail = mailer.doStuff
}

[info] Compiling 1 Scala source to ...
[error] /SomeOne/SomePath/SomeFile.scala:30: not enough arguments for constructor MyComponent: (mailerClient: play.api.libs.mailer.MailerClient) MyComponent.
[error] Unspecified value parameter mailerClient.
[error]   val mailer = new MyComponent
[error]                ^
[error] one error found
[error] (compile:compileIncremental) Compilation failed

虽然我可能会因为这个而接近:

How does @Inject in Scala work

这表明以下语法可以通过从构造函数中删除 @Inject 并将其放在字段上来工作。

@Inject var mailerClient: MailerClient = null

但是,当我们尝试运行任何需要该引用的东西时,我们仍然会得到 null。

我正在阅读我在@Inject 上可以找到的所有内容

( [警告] [咆哮] 由于这个确切的原因,我不喜欢这样的编译器魔法——巫术魔法非常棒,直到它停止工作,然后似乎没有人知道如何修复它。[/rant] [/warning] )

但我真正想知道的是如何正确、安全、有效地使用它。

【问题讨论】:

标签: scala playframework annotations inject


【解决方案1】:

这不是 scala 问题,而是 DI 问题。您应该阅读一些 guice 文档。

在 Play 2.4.x 中,需要使用依赖注入 (https://www.playframework.com/documentation/2.4.x/ScalaDependencyInjection) 来实现你的目标。

你的 AnObject 应该是:

@Singleton class AnObject @Inject()(mailer:MyComponent){
 def sendEmail = mailer.doStuff
}

【讨论】:

  • 嗯:Guice 旨在让开发和调试变得更容易和更快,而不是更难和更慢。在这种情况下,Guice 避开了惊喜和魔法。 ???
  • 工厂和单身人士现在看起来非常好。
  • 仍然没有解决原来的问题——我们只是把它移到另一个类——我如何调用(使用)这个 AnObject,因为现在编译器需要一个邮件参数 AnObject。请告诉我如何从一个对象中使用它——我意识到这与 Guice 试图做的相反——但至少我可以从那里得到它。
【解决方案2】:

由于您在原始 GitHub 存储库上关闭了您的问题,我不知道这个答案是否仍然必要,但由于您不完全了解 DI 框架的使用,我发现学习这项技能非常重要,我将尝试在这里解释它并列出一些好处。

首先,您实例化实例的方式不会让 DI 框架有机会注入您的依赖项。由于new 是语言关键字,因此DI 无法干扰,并且您的类所需的依赖项也无法注入。它是通过构造函数或字段注入来完成的。我将主要关注构造函数注入,因为这是 scala 世界中的“标准”。

如果您使用 @Injected 注释指定构造函数参数,则基本上是在告诉 DI 框架从容器中解析此依赖关系。 DI 框架会在其容器内查找该对象的条目。如果它不存在,它将创建它(并在此过程中解决它的依赖关系),如果它带有 @Singleton 注释,则还保存此实例以供将来使用。大多数 DI 框架要求您在大多数情况下指定起始类,但因为您使用的是 Play!框架这不是必需的。当您想在控制器中使用特定模块时,您可以这样做:

import javax.inject.Inject

import play.api.mvc.Controller

class Test @Inject() (val dependency: FooClass) extends Controller {
  ...
}

在这种情况下,FooClass 是您要注入控制器的依赖项的类名。假设 FooClass 有 Play 的 Application 作为依赖项,这将被注入,因为 Play 提供了几个预先绑定的预设,如 ApplicationActorSystem

这是可能的,因为 Play!框架使用DependencyInjectedRoutes。如果要在 Controller 之外创建 Actor,则需要在模块类中指定它,但这在 linklink 中有解释。

还有一个概念是在控制器内部使用Traits,然后将特征与实现类连接在一起,但我认为现在这有点太复杂了。

如果您想了解这种编写应用程序的方法的好处和成功案例,这里有一个很好的资源:@​​987654323@

如果您想了解有关此概念的内容:

我希望这可以解决问题!如果您有任何问题,请尽管提问!

【讨论】:

  • 我完全理解 DI 的目的和要求——这不是问题——我已经阅读了大部分 Guice 文档——包括与 @Inject 流程有关的所有内容(以及所有这些相关选项),但我仍然无法访问@Injected 对象。我认为 Play 有一些魔力可以让控制器拥有可注入的构造函数。文档中的某些内容暗示零参数构造函数是可验证的,而 @Injected 正在被它调用(反之亦然)。
  • 也许@Injected 对象只能在对象“内部”使用——但即使是这样,为什么每次尝试使用字段注入都会在运行时导致空指针?我正在 Global.scala 中启动(和绑定)Guice,我已经跟踪它以确保它实际上按预期加载。所以问题(和我原来的问题)仍然存在。我只想知道获取@Injected Objects 的缺失部分...
  • 这不回答问题,我也有同样的问题。如果在我想使用类时必须传入对象,那么在类构造函数中使用 DI 有什么意义
  • @Ir1sh 你没有。这就是重点。您事先定义实例,Guice 在必要时将它们连接起来。您仍然使用“我需要对所有内容使用 new 关键字”的心态。 Guice 无法拦截,您需要从模块类一路向上开始,然后从那里开始。在 Play 的情况下,您的模块类已经为您完成,因此您可以开始在控制器中定义依赖项(由 Play 自动在您的模块类中定义)。
  • @Ir1sh 抱歉我的诙谐评论。我会解释的。您可以通过以下方式实现您想要的:MyComponent @Inject (ws: WSClient)class bClass @Inject (ws: WSClient) extends MyComponent(ws)
【解决方案3】:

我遇到了同样的问题。我想创建一个具有邮件功能的类或对象,然后我可以在我想从任何控制器发送电子邮件时调用它。

我认为您所问的相当于如何在游戏框架之外使用mailerclient。据我了解,你不能。即使您在 app/controllers 文件夹中创建了一个类,它也只是一个常规的 scala 类,与 play 框架中的魔法无关。 @Inject 仅适用于控制器或模块。因为如果你创建一个独立的类,你必须在实例化它的时候自己注入一些东西。除非您正在构建模块或扩展控制器。您是否注意到 AppController 曾经是对象,而现在它是一个类?使用时不需要实例化它。我相信框架做了一些事情来通过配置(注入)来实例化它。

所以如果你想达到最初的设计目标,你要么编写一个模块,发布它,然后在所有控制器中使用它;或者使用 scala 中的一些其他电子邮件库来封装电子邮件发送功能。

【讨论】:

  • 当我痛苦地发现真正的答案是 Injected 类只能通过 Injected 类进行实例化(这显然就是海龟的意思)。您需要在控制器中执行此操作是不正确的 - 由于上述规则,它才出现。最早您可以得到“板载”的东西曾经是 GlobalSettings,现在已迁移到 play.modules.enabled 配置键。您可以创建自己的 Injectable 类,但它们也必须通过另一个 Injected 类(该死的乌龟堆)来获取。有点威士忌探戈狐步舞的情况恕我直言:)
  • 这就是我创建模块的意思。即使你可以用@inject创建一个类,但如果它不是控制器或模块,你仍然需要在使用时实例化,这在这种情况下是无能为力的。
【解决方案4】:

(我没有足够的声誉来发表评论,所以发布作为答案)

@aparo 发布的答案应标记为正确/批准的答案,因为它确实解决了问题。您说这并不能解决最初的问题,因为它将依赖项移至另一个类,但这只是部分正确,因为其他类只需要为您提供MyComponent 而不是MailerClient。虽然依赖注入需要一直使用到最终控制器(在 Play 的情况下),但您通常不必注入多个单个对象。

这方面的证据可以看到in a question I posted,因为我当时的心态和你一样。在我的示例中,我的控制器只需要一个 UserSearch 依赖项,DatabaseConfigurationProvider 依赖项由 guice 处理,因此我不需要在任何地方再次声明它。

【讨论】:

  • 我终于与 DI(或至少 Guice in Play)和平相处,而神奇的时刻是接受这样一个事实:为了获得注入组件,您必须首先在注入组件中。问题是(我怀疑)不止几个人会退出注射循环,因此像我一样提出一个永远无法令人满意地回答的问题。 (所有试验都导致 NULL 启发了文档似乎所说的内容。)我确实计划回来并记录 that 作为帮助其他人陷入困境的答案我在最长的时间...
猜你喜欢
  • 2015-09-30
  • 1970-01-01
  • 1970-01-01
  • 2016-02-26
  • 2015-12-16
  • 2015-10-08
  • 2015-12-21
  • 2017-01-27
  • 1970-01-01
相关资源
最近更新 更多