简体   繁体   中英

Scala Dependency Injection in Play Framework

I am new to Scala, the PlayFramework, and Dependency Injection. I downloaded the sample scala play framework code. Can someone explain to me why we need to inject the Clock and the appLifeCycle? It is referenced above, so there is no need to inject it right? What is going on here? Why do we need to do this for Web Frameworks in general?

package services

import java.time.{Clock, Instant}
import javax.inject._
import play.api.Logger
import play.api.inject.ApplicationLifecycle
import scala.concurrent.Future

/**
 * This class demonstrates how to run code when the
 * application starts and stops. It starts a timer when the
 * application starts. When the application stops it prints out how
 * long the application was running for.
 *
 * This class is registered for Guice dependency injection in the
 * [[Module]] class. We want the class to start when the application
 * starts, so it is registered as an "eager singleton". See the code
 * in the [[Module]] class to see how this happens.
 *
 * This class needs to run code when the server stops. It uses the
 * application's [[ApplicationLifecycle]] to register a stop hook.
 */
@Singleton
class ApplicationTimer @Inject() (clock: Clock, appLifecycle: ApplicationLifecycle) {

  // This code is called when the application starts.
  private val start: Instant = clock.instant
  Logger.info(s"ApplicationTimer demo: Starting application at $start.")

  // When the application starts, register a stop hook with the
  // ApplicationLifecycle object. The code inside the stop hook will
  // be run when the application stops.
  appLifecycle.addStopHook { () =>
    val stop: Instant = clock.instant
    val runningTime: Long = stop.getEpochSecond - start.getEpochSecond
    Logger.info(s"ApplicationTimer demo: Stopping application at ${clock.instant} after ${runningTime}s.")
    Future.successful(())
  }
}

I assume you're using Lightbend's Play Scala Seed , which contains the code sample that you posted.

If you look at the documentation for java.time.Clock , you'll notice it says (emphasis mine):

Best practice for applications is to pass a Clock into any method that requires the current instant. A dependency injection framework is one way to achieve this. {.. code sample omitted .. } This approach allows an alternate clock , such as fixed or offset to be used during testing.

Ultimately the purpose of dependency injection is to allow you to define the interface that you want to inject into a class or an object, and configure the implementation of that interface in just one spot. The alternative is having to update a hardcoded dependency in multiple files, which can be messy and error prone. In the Play Scala Seed project, you'll notice a file called app/Module.scala . This file is one place where you can configure the bindings and they'll automatically be bound on application start. Notice the line where we bind the Clock implementation:

class Module extends AbstractModule {

  override def configure() = {
    // Use the system clock as the default implementation of Clock
    bind(classOf[Clock]).toInstance(Clock.systemDefaultZone)
    // Ask Guice to create an instance of ApplicationTimer when the
    // application starts.
    bind(classOf[ApplicationTimer]).asEagerSingleton()
    // Set AtomicCounter as the implementation for Counter.
    bind(classOf[Counter]).to(classOf[AtomicCounter])
  }

}

This configuration is saying "when my application starts up, wherever I've injected a Clock should use Clock.systemDefaultZone ." If you want ApplicationTimer to use a different clock during tests, you might do something like this:

import play.api.{Environment, Mode}

// Notice that we include the environment
class Module(environment: Environment) extends AbstractModule {
  override def configure() = {
    // Use the system clock as the default implementation of Clock
    environment.mode match {
      case Mode.Prod | Mode.Dev => {
        bind(classOf[Clock]).toInstance(Clock.systemDefaultZone)
      }
      case Mode.Test => {
        // Specifically use UTC clock in tests, because maybe it's easier to write assertions that way
        // You could inject anything here and the ApplicationTimer would use it during tests
        bind(classOf[Clock]).toInstance(Clock.systemUTC())
      }
    }
    bind(classOf[ApplicationTimer]).asEagerSingleton()
    bind(classOf[Counter]).to(classOf[AtomicCounter])
  }
}

You can define modules in other places in the root package (ie no package com.example.whatever declaration at the top of the file) and they'll be automatically loaded, too. Otherwise, you'll need to add a binding in conf/application.conf that adds your module's name to the play.modules.enabled key. You can see a commented-out example of that in the Play Scala Seed. I also go into more depth in another answer that I've written here .

As for ApplicationLifecycle , this is a special module provided by Play that you could override with your own binding, but I'm not sure why you'd want to. It's there to give you access to hooks that execute before the application shuts down. Again, this component is injected because it would be simple to swap it out. Imagine having 100 modules that all depend on the application lifecycle. By default, it's bound to DefaultApplicationLifecycle (you can see it being bound here ). If you had hardcoded DefaultApplicationLifecycle in all 100 modules, you'd have to update each one if you wanted to switch to a different lifecycle. With dependency injection, you just need to configure the binding to use a different lifecycle and the 100 modules will automatically use it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM