【问题标题】:Does Guice DI create a new WSClient instance everytimeGuice DI 是否每次都创建一个新的 WSClient 实例
【发布时间】:2020-05-08 09:56:24
【问题描述】:

我有一个对外部供应商进行 API 调用的客户端类。在我的控制器中,我按如下方式注入客户端。

@Singleton
class AdsController @Inject()(client: MyClient)(
  implicit ec: ExecutionContext
) extends InjectedController {

  def index = Action.async(json.parse[ClientRequest])  {
     client.send(request.body).map({
       case s: SuccessResponse => Ok(Json.toJson(s))
       ...
     }) 
  }
}

客户端类如下所示


class MyClient @Inject()(ws: WSClient, appConfig: Configuration)
(implicit ec: ExecutionContext) {...}

我想了解两件事

  1. 注入器是否为每个请求注入一个新的 MyClient 实例?
  2. 如果是,注入器是否每次都注入一个新的 WSClient 和 Configuration 实例?

如果两者都是,则注入配置会不必要地创建新实例,这不是一个好主意。

【问题讨论】:

    标签: scala dependency-injection playframework guice ws-client


    【解决方案1】:

    Guice 默认总是创建一个新实例。除非您将其配置为通过例如重用实例将对象注释为@singleton 或配置使用某些缓存的Provider 或使用bind 显式指向在需要某些类时应使用的实例..

    这是一种理想的行为,因为有很多与使用单例相关的问题和错误(例如,使用单例更容易产生内存泄漏),所以这必须是程序员有意识的、明确的决定,谁应该做出确保它不会咬他们。

    【讨论】:

    • 谢谢,但我想确保每次都注入一个新的 WSClient 并注入一个配置实例。您的回答意味着每次请求命中时都会注入 Configuration 和 WSClient 新实例。
    • 如果您的配置中没有@singletonbindProvider 涉及WSClient,我会假设每次都有一个新实例。 (尽管您可以轻松检查它 - WSClient 不会覆盖 toStringhashCode 因此通过打印它们,您应该查看值是否相同或用法之间的变化)。
    • 抱歉,我错过了单身人士,正在更新问题。我会尝试打印 hashCode
    • MyClient 和 WSClient 每次都得到相同的哈希码。这意味着 WSClient 或 MyClient 不会每次都注入。我现在很困惑,如果发生两个并发请求,它们如何共享同一个对象。
    • WSClient 就像 Akka 中的所有其他东西一样,都是围绕并发构建的,并且在内部它都使用 ThreadPools 来运行任务。客户端返回Future/CompletionStage,因此它可以同时被任意数量的请求触发,它只会在线程池的队列中调度作业。
    【解决方案2】:

    Play 中的路由器是singleton

    @Singleton
    class RoutesProvider @Inject() (
        injector: Injector,
        environment: Environment,
        configuration: Configuration,
        httpConfig: HttpConfiguration
    ) extends Provider[Router] {
    
    ...
    
    bind[Router].toProvider[RoutesProvider]
    

    这实际上意味着即使控制器类没有使用@Singleton 注释,在请求之间注入的控制器实例也是by default reused,除非路由在routes 中以@ 运算符为前缀:

    ...如果您在控制器前面加上@ ...一个新的动作实例化 请求。

    例如,给定

    class MyClient
    
    class HomeController @Inject()(cc: ControllerComponents, client: MyClient) extends AbstractController(cc) {
      def index = Action {
        Ok(s"HomeController identity = ${this.hashCode}\nMyClient identity = ${client.hashCode}")
      }
    }
    
    

    以及下面的routes 文件

    GET     /                           controllers.HomeController.index
    

    尽管HomeController 不是单例,但不同的请求总是返回相同的身份

    // request 1
    HomeController identity = 409392943
    MyClient identity = 475611387
    
    // request 2
    HomeController identity = 409392943
    MyClient identity = 475611387
    

    但是如果我们在路由文件中使用@ 运算符

    GET     /                           @controllers.HomeController.index
    

    然后我们看到身份在每个新请求上都发生了变化:

    // request 1
    HomeController identity = 1249276649
    MyClient identity = 1152488919
    
    // request 2
    HomeController identity = 41809453
    MyClient identity = 213518354
    

    【讨论】:

    • 用@尝试了上述路线,标识似乎没有改变。
    • @Deepak 如果AdsController 被显式注释为@Singleton,则不会改变。
    • 啊,明白了,一个问题是这种方法很好,以防并发请求场景会导致任何问题,因为每个请求都将使用相同的实例?
    • @Deepak 使用单例时,程序员有责任确保控制器及其所有依赖项都是线程安全的。请参阅stackoverflow.com/questions/40643080/…
    • 非常感谢,我想确保上面写的 WSClient 的使用不会在并发请求处理场景中造成任何问题。
    猜你喜欢
    • 2012-10-07
    • 2018-10-16
    • 1970-01-01
    • 2015-11-06
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 2018-10-08
    • 1970-01-01
    相关资源
    最近更新 更多