0

I have a Client class that makes an API call to an external vendor. In my controller, I inject the client as follows.

@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))
       ...
     }) 
  }
}

The client class looks as follows


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

I wanted to understand two things

  1. Does the injector inject a new instance of MyClient for every request?
  2. If yes, Does the injector inject a new instance of WSClient and Configuration every time?

If both are yes then injecting configuration unnecessarily creates new instances which won't be a good idea.

Deepak
  • 341
  • 1
  • 10

2 Answers2

4

Guice by default always create a new instance. Unless you configured it to reuse the instance by e.g. annotating object as @singleton or configuring Provider that uses some cache or using bind to explicitly point to the instance that should be used when some class is required..

This is a desired behavior, as there are many issues and errors related to using singletons (e.g. it's easier to produce a memory leak with them), so this has to be a conscious, explicit decision of a programmer, who should make sure that it won't bite them.

Mateusz Kubuszok
  • 24,995
  • 4
  • 42
  • 64
  • Thanks, but I wanted to ensure that a new WSClient is injected every time and a single instance of configuration injected. Your answer would mean that both the Configuration and WSClient new instances are injected every time a request is hit. – Deepak May 08 '20 at 10:27
  • If in your config there is no `@singleton`, `bind` or `Provider` involving `WSClient`, I would assume that there is a new instance every time. (Though you can easily check it - `WSClient` doesn't override `toString` or `hashCode` so by printing them, you should see if value is the same or changes between usages). – Mateusz Kubuszok May 08 '20 at 10:31
  • Oops sorry, I missed the Singleton, updating the question. I will try printing hashCode – Deepak May 08 '20 at 11:17
  • I am getting the same hashcode every time for MyClient and for WSClient. Which means WSClient or MyClient is not injected everytime. I am puzzled now, If two concurrent request happens how does they share the same object. – Deepak May 08 '20 at 11:39
  • 1
    `WSClient` like everything else in Akka is build around concurrency, and internally it all uses ThreadPools to run tasks. Client returns `Future`/`CompletionStage` so it can be triggered by any number of requests at once and it will just schedule jobs on queue in ThreadPool. – Mateusz Kubuszok May 08 '20 at 11:52
0

The router in Play is a singleton

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

...

bind[Router].toProvider[RoutesProvider]

which effectively means even if controller class is not annotated with @Singleton the injected controller instance is by default reused between requests, unless the route is prefixed with @ operator in the routes:

...if you prefix a controller with @ ... a new action instantiated per request.

For example, given

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}")
  }
}

and the following routes file

GET     /                           controllers.HomeController.index

different requests always return the same identity despite HomeController not being a singleton

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

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

however if we use @ operator in the routes file

GET     /                           @controllers.HomeController.index

then we see identity is changing on each new request:

// request 1
HomeController identity = 1249276649
MyClient identity = 1152488919

// request 2
HomeController identity = 41809453
MyClient identity = 213518354
Mario Galic
  • 47,285
  • 6
  • 56
  • 98
  • Tried the above routes line with @, the identity doesn't seem to change. – Deepak May 08 '20 at 12:45
  • 1
    @Deepak It will not change if `AdsController` is explicitly annotated with `@Singleton`. – Mario Galic May 08 '20 at 12:48
  • Ahh Got it, One question is this approach fine in case of concurrent requests scenario will cause any problem becaue every request will use the same instance ? – Deepak May 08 '20 at 12:50
  • 1
    @Deepak When using singletons the onus is on the programmer to ensure the controller and all its dependencies are thread-safe. Please see https://stackoverflow.com/questions/40643080/why-use-singleton-controllers-in-play-2-5 – Mario Galic May 08 '20 at 12:59
  • Great thanks, I wanted to make sure the usage of WSClient as I have written above doesn't cause any problem in a concurrent request processing scenario. – Deepak May 10 '20 at 12:16