When you add a dependency to a controller (directly or indirectly) or when you refactor a class - if you forget to wire a dependency in your module, you will encounter this error at runtime.
We would like to tackle this problem.
The main advantage of runtime dependency injection is that it is simple once you’re comfortable with it.
Most of the times, an injected parameter of type T will either have a default constructor, and then Guice will invoke it at runtime, or it will not, and then Guice will look under a module class for a method annotated with @Provides
that returns an instance of T, and then Guice will invoke it, again - at runtime.
Dependencies of dependencies will be instantiated by Guice in the same way, by invoking their default constructor / provider method at runtime.
Scala and Java have a special keyword for compile time dependency injection called new
😉
Remove Guice
Let’s remove the runtime dependency injection:
- Delete the module
com.example.playground.configuration.Module.scala
- Remove this module from the registered modules in
application.conf
by deleting the keyplay.modules.enabled
along with its corresponding value - Remove the guice jar from the project by deleting
libraryDependencies += guice
frombuild.sbt
.
Utilize Play’s Built In Components
Add a new class com.example.playground.configuration.components.AppComponents
that inherits from Play’s BuiltInComponentsFromContext
and initializes the application’s components:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.example.playground.configuration.components
import controllers.{AssetsComponents, HomeController}
import play.api.ApplicationLoader.Context
import play.api.BuiltInComponentsFromContext
import play.api.routing.Router
import play.filters.HttpFiltersComponents
import pureconfig.ConfigSource
import pureconfig.generic.auto._
import router.Routes
import com.example.playground.configuration.Config
class AppComponents(context: Context) extends BuiltInComponentsFromContext(context)
with HttpFiltersComponents
with AssetsComponents {
lazy val config: Config = ConfigSource.default.loadOrThrow[Config]
lazy val homeController: HomeController = new HomeController(controllerComponents, config)
override def router: Router = new Routes(httpErrorHandler, homeController, assets)
}
Play’s application loader, which we will create shortly, should return an application (play.api.Application, to be precise).
In order to initialize the application, you should initialize its dependencies, such as its environment, request handler, error handler, etc.
Luckily, play provides an abstract class called BuiltInComponentsFromContext
that initializes many of these components.
You may mix-in other SomeComponents traits to get more components, like we did with HttpFiltersComponents
and AssetsComponents
.
BuiltInComponentsFromContext
leaves 2 unimplemented members:
httpFilters
: a sequence of filters that run on the request headers for every request.
You can implement it as an empty sequence as a start, or get play’s recommended filters by mixing-inHttpFiltersComponents
like we did.router
: routes the requests to their designated controller.
TheRoutes
class is built from ourconf/routes
file upon compilation.
You can peek at its constructor and see your HomeController for example.
In order to instantiateRoutes
we need to create HomeController andAssets
instances.
We can get a default implementation forAssets
by mixing-inAssetsComponents
, and in order to create HomeController we need to createConfig
.
Load the application
Create a class com.example.playground.configuration.AppLoader
that instantiates the components we just created, and returns a new play application:
1
2
3
4
5
6
7
8
9
10
11
package com.example.playground.configuration
import play.api._
import play.api.ApplicationLoader.Context
import com.example.playground.configuration.components.AppComponents
class AppLoader extends ApplicationLoader {
def load(context: Context): Application = {
new AppComponents(context).application
}
}
Set the AppLoader as the entry point in application.conf
by adding play.application.loader
setting. e.g your play’s configuration blob may look like this:
1
2
3
4
play {
http.secret.key="MePzCIzgeI8jhPfWg8RPGCFvLobM6K8bnCabNgSdDBc="
application.loader = com.example.playground.configuration.AppLoader
}
Run the app by running run
inside the sbt shell and browse to http://localhost:9000/.
You should see the config.