Play 2.4: Schedule a recurring task at app startup

2019-02-24 22:38发布

问题:

I need to schedule a recurring task on the application start, the task itself is very simple just send to the application a fire-and-forget HTTP call. I'm not a play expert, buy i would assume that s straightforward solution would be something like using play.api.libs.concurrent.Akka.system.schedule in Global.onStart. Since Play 2.4, Global configuration is somewhat deprecated in favor of new Guice DI. Hacking the advice from the DI documentation i couldn't come up with a nice solution for this issue. The best i managed to get is writing a wrapper on top of GuiceApplicationLoader calling a custom implementation of BuiltInComponentsFromContext, but in this case i can't use injection to get WSClient. What's the best way to rewrite something like this with Play 2.4:

object Global extends GlobalSettings {
  override def onStart(app: Application) = {
    Akka.system.schedule(2.hours, 2.hours, theTask)
  }
}

回答1:

Update: this is now better documented for Play 2.6: https://www.playframework.com/documentation/2.6.x/ScheduledTasks


You can solve this by creating a module like this (attention to code comments):

package tasks

import javax.inject.{Singleton, Inject}

import akka.actor.ActorSystem
import com.google.inject.AbstractModule

import play.api.inject.ApplicationLifecycle

// Using the default ExecutionContext, but you can configure
// your own as described here:
// https://www.playframework.com/documentation/2.4.x/ThreadPools
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.Future
import scala.concurrent.duration._

class MyRecurrentTaskModule extends AbstractModule {
  override def configure() = {
    // binding the RecurrentTask as a eager singleton will force
    // its initialization even if RecurrentTask is not injected in
    // any other object. In other words, it will starts with when
    // your application starts.
    bind(classOf[RecurrentTask]).asEagerSingleton()
  }
}

@Singleton
class RecurrentTask @Inject() (actorSystem: ActorSystem, lifecycle: ApplicationLifecycle) {

  // Just scheduling your task using the injected ActorSystem
  actorSystem.scheduler.schedule(1.second, 1.second) {
    println("I'm running...")
  }

  // This is necessary to avoid thread leaks, specially if you are
  // using a custom ExecutionContext
  lifecycle.addStopHook{ () =>
    Future.successful(actorSystem.shutdown())
  }

}

After that, you must enable this module adding the following line in your conf/application.conf file:

play.modules.enabled += "tasks.MyRecurrentTaskModule"

Then, just start you application, fire a request to it and see the scheduled task will run every each second.

References:

  1. Understanding Play thread pools
  2. Play Runtime Dependency Injection for Scala
  3. Integrating with Akka

Related questions:

  1. How to correctly schedule task in Play Framework 2.4.2 scala?
  2. Was asynchronous jobs removed from the Play framework? What is a better alternative?