Roy Lin
Roy Lin

Reputation: 760

playframework 2.4 GlobalSettings onStart Deprecated

I am migrating my app from play 2.3 to 2.4.

In my 2.3 app in my GlobalSettings, I had to access the database using slick to create a postgres database function.

Since GlobalSettings is deprecated in 2.4, the alternative is to use Eager Bindings:

https://www.playframework.com/documentation/2.4.x/ScalaDependencyInjection#Eager-bindings

like this:

class MyModule extends AbstractModule {
  def configure() = {
    db.withSession { implicit ss =>
      StaticQuery.update("""CREATE OR REPLACE FUNCTION ... """).execute
    }
  }
}

But this gives me the error:

java.lang.ExceptionInInitializerError: 
     core.includes$.<init>(includes.scala:14)
     core.includes$.<clinit>(includes.scala)
     Application$$anonfun$configure$1.apply(Application.scala:17)
     Application$$anonfun$configure$1.apply(Application.scala:15)
     scala.slick.backend.DatabaseComponent$DatabaseDef$class.withSession(DatabaseComponent.scala:34)
     scala.slick.jdbc.JdbcBackend$DatabaseFactoryDef$$anon$4.withSession(JdbcBackend.scala:61)
     modules.jdbc.Database$$anonfun$withSession$1.apply(Database.scala:14)
     modules.jdbc.Database$$anonfun$withSession$1.apply(Database.scala:14)
     Application.configure(Application.scala:15)
     com.google.inject.AbstractModule.configure(AbstractModule.java:62)
     com.google.inject.spi.Elements$RecordingBinder.install(Elements.java:340)
     com.google.inject.spi.Elements.getElements(Elements.java:110)
     com.google.inject.util.Modules$OverrideModule.configure(Modules.java:177)
     com.google.inject.AbstractModule.configure(AbstractModule.java:62)
     com.google.inject.spi.Elements$RecordingBinder.install(Elements.java:340)
     com.google.inject.spi.Elements.getElements(Elements.java:110)
     com.google.inject.internal.InjectorShell$Builder.build(InjectorShell.java:138)
     com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:104)
     com.google.inject.Guice.createInjector(Guice.java:96)
     com.google.inject.Guice.createInjector(Guice.java:73)
     com.google.inject.Guice.createInjector(Guice.java:62)
     play.api.inject.guice.GuiceBuilder.injector(GuiceInjectorBuilder.scala:126)
     play.api.inject.guice.GuiceApplicationBuilder.build(GuiceApplicationBuilder.scala:93)
     play.api.inject.guice.GuiceApplicationLoader.load(GuiceApplicationLoader.scala:21)

Does anyone have a clue on how I can fix this? Thanks.

Upvotes: 2

Views: 4431

Answers (1)

pro_zw
pro_zw

Reputation: 391

Accroding to the quotation from https://www.playframework.com/documentation/2.4.2/GlobalSettings

GlobalSettings.beforeStart and GlobalSettings.onStart: Anything that needs to happen on start up should now be happening in the constructor of a dependency injected class. A class will perform its initialisation when the dependency injection framework loads it. If you need eager initialisation (because you need to execute some code before the application is actually started), define an eager binding.

As you can see, the eager binding can only be used if something needs happen before the application starts, which is not applicable in your situation, as db.withSession requires a started application context. That is why the exception occurs (BTW strictly speaking, you are not using eager binding in the appropriate way).

So how can you achieve the goal? The answer is in the first two sentences in the quotation.

First of all, you must define something like:

@Singleton
class DatabaseService {
    db.withSession { implicit ss =>
      StaticQuery.update("""CREATE OR REPLACE FUNCTION ... """).execute
    }
}

And then if you inject the DatabaseService into another singleton class (you'd better inject it into another singleton class, or the code may be called multiple times), and the later class is initialized by the Guice, the code in the DatabaseService's constructor is called (because the DatabaseService is initialized firstly by Guice as the dependency of the later class).

For example, you can inject it into a controller:

@Singleton
class Application @Inject() (dbService: DatabaseService) extends Controller {
  def index = Action {
    Ok(views.html.index("Your new application is ready."))
  }
}

and then if anyone accesses the index page, your code will be executed.

EDIT:

I found another stackoverflow post on the topic, please see here: PlayFramework 2.4 run some code after application has started. It figures out the correct way to run the code after the application starts while still using eager binding. :)

If you depends on ActorSystem, just inject actorSystem: ActorSystem into your class, like this:

@Singleton
class QuartzSchedulerService @Inject() (configuration: Configuration,
                                        actorSystem: ActorSystem,
                                        @Named("library.actors.ApiExecutionRecorderRouter") apiExecutionRecorderRouter: ActorRef
                                        ) {
  val scheduler = QuartzSchedulerExtension(actorSystem)
  scheduler.schedule("QuartzSchedulerTest", apiExecutionRecorderRouter, "Start")
}

Upvotes: 6

Related Questions