[2.4 Scala] Run code right after Play is started

1,258 views
Skip to first unread message

René Grüner Vangsgaard

unread,
May 12, 2016, 3:01:35 PM5/12/16
to play-framework
Hello

When migrating from Global.onStart() to dependency injection I miss the ability to run code after the app is started. I use it to fill caches with data from the database. I would like that to happen even though no requests are coming in yet. How can I achieve this?

Thank you,
René

Justin Nichols

unread,
May 12, 2016, 3:04:21 PM5/12/16
to play-framework
You can create your own eagerly instantiated module and in its constructor you can run your startup code (that you were previously doing in Global.onStart()).  I also believe there is a convention of having a class named "Module" in the root of your source to also do this, but offhand I can't find the documentation on that.

--
You received this message because you are subscribed to the Google Groups "play-framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framewor...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/play-framework/80e8fe83-be7c-40b2-af6d-ef7802868c26%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
--
Thanks,

Justin

René Grüner Vangsgaard

unread,
May 12, 2016, 3:43:53 PM5/12/16
to play-framework
The problem with that approach is that the Play app is not initialised (or running). So I am missing configuration and database connections when using the eager binding.

Regards,
René

Justin Nichols

unread,
May 12, 2016, 3:46:58 PM5/12/16
to play-framework
You should be able to add those as injected parameters to your constructor and then they'll be instantiated prior to your constructor running the startup sequence.

Thanks,

Justin




René Grüner Vangsgaard

unread,
May 13, 2016, 1:49:46 AM5/13/16
to play-framework
Let me see if I can explain, and have understood it correctly.

1. Dependency injected objects with eager binding are created before the Play application is initialised. This point in time does not make sense to me, as I need database access.

2. Play application starts. After this, configuration and database access are available.

3. Request comes in, DI framework instantiates required objects to serve the request.

Performing the "cache filler" code at point 3 is too late.

I would like to run some code right after point 2. I cannot see that your suggestion allow this - what am I missing?

Thanks, René

Justin Nichols

unread,
May 13, 2016, 8:47:05 AM5/13/16
to play-fr...@googlegroups.com
René,

I'm sorry for the confusion, I replied in haste as I didn't have much time yesterday.

Here is how you can set things up:

1) Create a class called modules.MyModule with the following contents:
package modules;

import com.google.inject.AbstractModule;

public class MyModule extends AbstractModule {
    @Override
    public void configure() {
        bind(AppStartup.class).asEagerSingleton();
    }
}

2) Create a class called modules.AppStartup with the following contents (this assumes you're using Ebean for database access):
package modules;

import play.Configuration;
import play.Environment;
import play.Logger;
import play.inject.ApplicationLifecycle;
import play.db.ebean.EbeanConfig;
import play.db.ebean.EbeanDynamicEvolutions;

class AppStartup {
    private final Environment environment;
    private final Configuration configuration;

    @Inject
    public AppStartup(ApplicationLifecycle lifecycle, Environment environment, Configuration configuration, EbeanDynamicEvolutions dynamicEvolutions, EbeanConfig ebeanConfig) {
        // adding EbeanDynamicEvolutions and EbeanConfig will allow Ebean to start up prior to this constructor running
        this.environment = environment;
        this.configuration = configuration;

        // onStart
        onStart();

        lifecycle.addStopHook( () -> {
            // onStop
            return CompletableFuture.completedFuture(null);
        }
    }

    public void onStart() {
        Logger.debug("Configuration item: " + this.configuration.getString("foo.bar.baz"));

        // Commented the following out since it's just to show using Ebean and won't actually work unless you have a BeanClass class, a database, etc:
        //
        // BeanClass bc = Ebean.find(BeanClass.class).where().eq("id", 1).findUnique();
        // if (bc != null) {
        //     Logger.debug("Ebean stuff: " + bc.toString());
        // }
    }
}

3) In your conf/ folder, create a file called reference.conf with the following contents:
play.modules.enabled += "modules.MyModule"

4) Run the application, and you can breakpoint debug into the AppStartup class to see it run and see that things are injected and working.


I believe that is enough to give yourself a startup module allowing you to access everything you previously accessed in Global (but using injected components instead of statics).  I didn't compile the above code, but I believe it is correct.  I have done this in practice and know that it works.  There is access to Configuration and to a Database in the AppStartup.onStart() (a method which could be named anything, I just made it that to show it's akin to Global.onStart()).

I hope this helps!




For more options, visit https://groups.google.com/d/optout.
--
Thanks,

Justin

Justin Nichols

unread,
May 13, 2016, 8:48:05 AM5/13/16
to play-fr...@googlegroups.com
I also just realized you may be using Scala.  You will have to translate this code to Scala to properly work.  I've somewhat learned Scala, but nowhere near the level to be helpful in it.  The concepts will translate, however.
--
Thanks,

Justin

René Grüner Vangsgaard

unread,
May 13, 2016, 9:21:45 AM5/13/16
to play-framework
Justin, thanks

Your advise helped me crack the nut. I made it work by binding the relevant beans with asEagerSingleton().

I "think" I tried this earlier but some sub systems of Play were not started up, but I did it wrong, obviously, as trying it with your code in mind made it work.

I must have accessed Play.current or something directly.... 

Once again, thanks 

Justin Nichols

unread,
May 13, 2016, 9:23:13 AM5/13/16
to play-framework
Awesome!  Glad I could be of a little help there.  Good luck!


For more options, visit https://groups.google.com/d/optout.
--
Thanks,

Justin

René Grüner Vangsgaard

unread,
May 13, 2016, 3:33:36 PM5/13/16
to play-framework
Hmmm, after further investigation, I found out that I misunderstood the timing of eager initialisation. Eager initialisation is still only performed when the application is accessed in any way - that is still too late. 

I would like the application to warm all its caches immediately after startup. I will not wait until someone accesses the application. Is this possible?

Regards,
René

Justin Nichols

unread,
May 13, 2016, 3:43:58 PM5/13/16
to play-framework
What I described earlier allows you to, in the onStart() of the AppStartup to set up all caches you see fit.  You have to get away from the notion of statics.  So if you're accessing caches using statics, that's a problem (and possibly /the/ problem).  You should turn those classes into @Singleton objects and @Inject them into any controllers that need access to those caches.  Each of those @Singleton objects can have everything @Inject-ed in them that you need to initialize them (such as Environment, Configuration, other Database modules, etc).

I'm sorry if I'm not more help than this.  I don't have much time to write an example using @Singleton

Thanks,

Justin

_____________________________
From: René Grüner Vangsgaard <rene.va...@gmail.com>
Sent: Friday, May 13, 2016 3:33 PM
Subject: Re: [play-framework] [2.4 Scala] Run code right after Play is started
To: play-framework <play-fr...@googlegroups.com>

Greg Methvin

unread,
May 13, 2016, 4:12:59 PM5/13/16
to play-framework
To add to Justin's answer, if you are using any of Play's (deprecated in 2.5) static references such as Play.current, then that will not work until after the injector is fully initialized. First the injector is created (and eager singletons initialized), then the application is instantiated from the injector, then Play.start is called to set the static reference.

I'm not sure what you mean "immediately after startup". What does it mean for the application to be "started"? Application is just a container for several different components started together. It should be perfectly fine to start at the same time as other components, as long as you declare your dependencies properly (usually through constructor injection).

If you are looking for a smoother migration path from static references, you may want to investigate Guice's static injection support to set your own static references as you work on migrating your code.

Greg


For more options, visit https://groups.google.com/d/optout.



--
Greg Methvin
Senior Software Engineer

René Grüner Vangsgaard

unread,
May 15, 2016, 5:19:05 PM5/15/16
to play-framework
I am not using statics, I have switched to dependency injection. The cache-filler is bound in StartupModule like bind().asEagerSingleton().

What I am experiencing is this (when running "sbt ~run"):
  1. Some "core" framework initialisation takes place, for example the logger.
  2. Nothing happens. The app idles.
  3. I access a random endpoint (controller). This endpoint might not depend on the cache-filler, it doesn't really matter.
  4. The DI injector kicks in, and start eager singletons, including the cache-filler.
I would like the cache-filler code to run at point 2. But it seems like nothing happens (even the eager singletons). Am I understanding this course of events correctly?

Here is my binding code:
class StartupModule extends AbstractModule {
 
override def configure(): Unit = {
    bind
(classOf[BeforeAppStartup])
     
.asEagerSingleton()
 
}
}

Thanks, 
René

Greg Methvin

unread,
May 15, 2016, 6:12:13 PM5/15/16
to play-framework
I think this is just a result of the way dev mode works. The server starts first and does some initialization that is not app-specific (for example the logger). Then when you make a request it actually creates your app.

I'm guessing if you try to run in production you won't see the same issue.


For more options, visit https://groups.google.com/d/optout.

René Grüner Vangsgaard

unread,
May 17, 2016, 3:40:17 PM5/17/16
to play-framework
Greg, you are right - it is behaving correctly in production: the eagerSingletons are instantiated immediately after Play is started. In dev-mode it behaves differently.

Thanks for helping me out.
Reply all
Reply to author
Forward
0 new messages