Does Guice DI create a new WSClient instance everytime

233 views Asked by At

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.

2

There are 2 answers

5
Mateusz Kubuszok On BEST ANSWER

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.

5
Mario Galic On

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