Skip to content

Commit

Permalink
Support Java ApplicationLoaders; add GuiceApplicationLoader docs
Browse files Browse the repository at this point in the history
This will mostly be useful because it makes it easier to override the
GuiceApplicationLoader.
  • Loading branch information
richdougherty authored and Ben McCann committed May 2, 2015
1 parent 1cbf3de commit 22c115d
Show file tree
Hide file tree
Showing 15 changed files with 428 additions and 25 deletions.
Expand Up @@ -134,3 +134,18 @@ In order to maximise cross framework compatibility, keep in mind the following t
If there is a module that you don't want to be loaded, you can exclude it by appending it to the `play.modules.disabled` property in `application.conf`:

play.modules.disabled += "play.api.db.evolutions.EvolutionsModule"

## Advanced: Extending the GuiceApplicationLoader

Play's runtime dependency injection is bootstrapped by the [`GuiceApplicationLoader`](api/java/play/inject/guice/GuiceApplicationLoader.html) class. This class loads all the modules, feeds the modules into Guice, then uses Guice to create the application. If you want to control how Guice initializes the application then you can extend the `GuiceApplicationLoader` class.

There are several methods you can override, but you'll usually want to override the `builder` method. This method reads the [`ApplicationLoader.Context`](api/java/play/ApplicationLoader.Context.html) and creates a [`GuiceApplicationBuilder`](api/java/play/inject/guice/GuiceApplicationBuilder.html
). Below you can see the standard implementation for `builder`, which you can change in any way you like. You can find out how to use the `GuiceApplicationBuilder` in the section about [[testing with Guice|JavaTestingWithGuice]].

@[custom-application-loader](code/javaguide/advanced/di/guice/CustomApplicationLoader.java)

When you override the [`ApplicationLoader`](api/java/play/ApplicationLoader.html) you need to tell Play. Add the following setting to your `application.conf`:

play.application.loader := "modules.CustomApplicationLoader"

You're not limited to using Guice for dependency injection. By overriding the `ApplicationLoader` you can take control of how the application is initialized.
Expand Up @@ -3,18 +3,25 @@
*/
package javaguide.advanced.di.guice;

//#guice-app-loader
import play.api.Application;
import play.api.ApplicationLoader;
import play.api.inject.guice.GuiceApplicationLoader;
//#custom-application-loader
import play.Application;
import play.ApplicationLoader;
import play.Configuration;
import play.inject.guice.GuiceApplicationBuilder;
import play.inject.guice.GuiceApplicationLoader;
import play.libs.Scala;

public class CustomApplicationLoader extends GuiceApplicationLoader {

@Override
public Application load(ApplicationLoader.Context context) {
// TODO: document how to create a Guice Module for the builder which relies on configuration settings
return builder(context).build();
}
@Override
protected GuiceApplicationBuilder builder(
ApplicationLoader.Context context) {
Configuration extra = new Configuration("a = 1");
return initialBuilder
.in(context.environment())
.loadConfig(extra.withFallback(context.initialConfiguration()))
.overrides(overrides(context));
}

}
//#guice-app-loader
//#custom-application-loader
Expand Up @@ -132,3 +132,17 @@ In order to maximise cross framework compatibility, keep in mind the following t
If there is a module that you don't want to be loaded, you can exclude it by appending it to the `play.modules.disabled` property in `application.conf`:

play.modules.disabled += "play.api.db.evolutions.EvolutionsModule"

## Advanced: Extending the GuiceApplicationLoader

Play's runtime dependency injection is bootstrapped by the [`GuiceApplicationLoader`](api/scala/index.html#play.api.inject.guice.GuiceApplicationLoader) class. This class loads all the modules, feeds the modules into Guice, then uses Guice to create the application. If you want to control how Guice initializes the application then you can extend the `GuiceApplicationLoader` class.

There are several methods you can override, but you'll usually want to override the `builder` method. This method reads the [`ApplicationLoader.Context`](api/scala/index.html#play.api.ApplicationLoader$$Context) and creates a [`GuiceApplicationBuilder`](api/scala/index.html#play.api.inject.guice.GuiceApplicationBuilder). Below you can see the standard implementation for `builder`, which you can change in any way you like. You can find out how to use the `GuiceApplicationBuilder` in the section about [[testing with Guice|ScalaTestingWithGuice]].

@[custom-application-loader](code/RuntimeDependencyInjection.scala)

When you override the [`ApplicationLoader`](api/scala/index.html#play.api.ApplicationLoader) you need to tell Play. Add the following setting to your `application.conf`:

play.application.loader := "modules.CustomApplicationLoader"

You're not limited to using Guice for dependency injection. By overriding the `ApplicationLoader` you can take control of how the application is initialized. Find out more in the [[next section|ScalaCompileTimeDependencyInjection]].
Expand Up @@ -221,4 +221,28 @@ package injected.controllers {
class Application {
def index = Action(Results.Ok)
}
}

package customapplicationloader {

import play.api.{Configuration, Environment}

import implemented._

//#custom-application-loader
import play.api.ApplicationLoader
import play.api.Configuration
import play.api.inject._
import play.api.inject.guice._

class CustomApplicationLoader extends GuiceApplicationLoader() {
override def builder(context: ApplicationLoader.Context): GuiceApplicationBuilder = {
val extra = Configuration("a" -> 1)
initialBuilder
.in(context.environment)
.loadConfig(extra ++ context.initialConfiguration)
.overrides(overrides(context): _*)
}
}
//#custom-application-loader
}
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package play.inject.guice;

import play.api.inject.guice.GuiceableModule;
import play.libs.Scala;
import play.Application;
import play.ApplicationLoader;

/**
* An ApplicationLoader that uses Guice to bootstrap the application.
*
* Subclasses can override the <code>builder</code> and <code>overrides</code>
* methods.
*/
public class GuiceApplicationLoader implements ApplicationLoader {

/**
* The initial builder to start construction from.
*/
protected final GuiceApplicationBuilder initialBuilder;

public GuiceApplicationLoader() {
this(new GuiceApplicationBuilder());
}

public GuiceApplicationLoader(GuiceApplicationBuilder initialBuilder) {
this.initialBuilder = initialBuilder;
}

@Override
public final Application load(ApplicationLoader.Context context) {
return builder(context).build();
}

/**
* Construct a builder to use for loading the given context.
*/
protected GuiceApplicationBuilder builder(ApplicationLoader.Context context) {
return initialBuilder
.in(context.environment())
.loadConfig(context.initialConfiguration())
.overrides(overrides(context));
}

/**
* Override some bindings using information from the context. The default
* implementation of this method provides bindings that most applications
* should include.
*/
protected GuiceableModule[] overrides(ApplicationLoader.Context context) {
scala.collection.Seq<GuiceableModule> seq = play.api.inject.guice.GuiceApplicationLoader$.MODULE$.defaultOverrides(context.underlying());
return Scala.asArray(GuiceableModule.class, seq);
}

}
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package play.inject.guice;

import com.google.common.collect.ImmutableMap;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
import org.junit.Test;
import play.Application;
import play.ApplicationLoader;
import play.Configuration;
import play.Environment;
import play.libs.Scala;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static play.inject.Bindings.bind;

public class GuiceApplicationLoaderTest {

@Rule
public ExpectedException exception = ExpectedException.none();

private ApplicationLoader.Context fakeContext() {
return ApplicationLoader.Context.create(Environment.simple());
}

@Test
public void additionalModulesAndBindings() {
GuiceApplicationBuilder builder = new GuiceApplicationBuilder()
.bindings(new AModule())
.bindings(bind(B.class).to(B1.class));
ApplicationLoader loader = new GuiceApplicationLoader(builder);
Application app = loader.load(fakeContext());

assertThat(app.injector().instanceOf(A.class), instanceOf(A1.class));
assertThat(app.injector().instanceOf(B.class), instanceOf(B1.class));
}

@Test
public void extendLoaderAndSetConfiguration() {
ApplicationLoader loader = new GuiceApplicationLoader() {
@Override
protected GuiceApplicationBuilder builder(ApplicationLoader.Context context) {
Configuration extra = new Configuration("a = 1");
return initialBuilder
.in(context.environment())
.loadConfig(extra.withFallback(context.initialConfiguration()))
.overrides(overrides(context));
}
};
Application app = loader.load(fakeContext());

assertThat(app.configuration().getInt("a"), is(1));
}

public static interface A {}
public static class A1 implements A {}

public static class AModule extends com.google.inject.AbstractModule {
public void configure() {
bind(A.class).to(A1.class);
}
}

public static interface B {}
public static class B1 implements B {}

}
139 changes: 139 additions & 0 deletions framework/src/play/src/main/java/play/ApplicationLoader.java
@@ -0,0 +1,139 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package play;

import java.util.Collections;
import java.util.Map;
import play.core.SourceMapper;
import play.core.DefaultWebCommands;
import play.libs.Scala;

/**
* Loads an application. This is responsible for instantiating an application given a context.
*
* Application loaders are expected to instantiate all parts of an application, wiring everything together. They may
* be manually implemented, if compile time wiring is preferred, or core/third party implementations may be used, for
* example that provide a runtime dependency injection framework.
*
* During dev mode, an ApplicationLoader will be instantiated once, and called once, each time the application is
* reloaded. In prod mode, the ApplicationLoader will be instantiated and called once when the application is started.
*
* Out of the box Play provides a Java and Scala default implementation based on Guice. The Java implementation is the
* {@link play.inject.guice.GuiceApplicationLoader} and the Scala implementation is {@link play.api.inject.guice.GuiceApplicationLoader}.
*
* A custom application loader can be configured using the `application.loader` configuration property.
* Implementations must define a no-arg constructor.
*/
public interface ApplicationLoader {

/**
* Load an application given the context.
*/
Application load(ApplicationLoader.Context context);

/**
* The context for loading an application.
*/
final static class Context {
private final play.api.ApplicationLoader.Context underlying;
private final Configuration initialConfiguration;
private final Environment environment;

/**
* The context for loading an application.
*
* @param underlying The Scala context that is being wrapped.
*/
public Context(play.api.ApplicationLoader.Context underlying) {
this.underlying = underlying;
this.environment = new Environment(underlying.environment());
this.initialConfiguration = new Configuration(underlying.initialConfiguration());
}

/**
* Get the wrapped Scala context.
*/
public play.api.ApplicationLoader.Context underlying() {
return underlying;
}

/**
* Get the environment from the context.
*/
public Environment environment() {
return environment;
}

/**
* Get the configuration from the context. This configuration is not necessarily the same
* configuration used by the application, as the ApplicationLoader may, through it's own
* mechanisms, modify it or completely ignore it.
*/
public Configuration initialConfiguration() {
return initialConfiguration;
}

/**
* Create a new context with a different environment.
*/
public Context withEnvironment(Environment environment) {
play.api.ApplicationLoader.Context scalaContext = new play.api.ApplicationLoader.Context(
environment.underlying(),
underlying.sourceMapper(),
underlying.webCommands(),
underlying.initialConfiguration());
return new Context(scalaContext);
}

/**
* Create a new context with a different configuration.
*/
public Context withConfiguration(Configuration initialConfiguration) {
play.api.ApplicationLoader.Context scalaContext = new play.api.ApplicationLoader.Context(
underlying.environment(),
underlying.sourceMapper(),
underlying.webCommands(),
initialConfiguration.getWrappedConfiguration());
return new Context(scalaContext);
}

// The following static methods are on the Context inner class rather
// than the ApplicationLoader interface because https://issues.scala-lang.org/browse/SI-8852
// wasn't fixed until Scala 2.11.3, and at the time of writing we need
// to support Scala 2.10.

/**
* Create an application loading context.
*
* Locates and loads the necessary configuration files for the application.
*
* @param environment The application environment.
* @param initialSettings The initial settings. These settings are merged with the settings from the loaded
* configuration files, and together form the initialConfiguration provided by the context. It
* is intended for use in dev mode, to allow the build system to pass additional configuration
* into the application.
*/
public static Context create(Environment environment, Map<String, String> initialSettings) {
play.api.ApplicationLoader.Context scalaContext = play.api.ApplicationLoader$.MODULE$.createContext(
environment.underlying(),
Scala.asScala(initialSettings),
Scala.<SourceMapper>None(),
new DefaultWebCommands());
return new Context(scalaContext);
}

/**
* Create an application loading context.
*
* Locates and loads the necessary configuration files for the application.
*
* @param environment The application environment.
*/
public static Context create(Environment environment) {
return create(environment, Collections.emptyMap());
}

}

}
7 changes: 7 additions & 0 deletions framework/src/play/src/main/java/play/Configuration.java
Expand Up @@ -72,6 +72,13 @@ public Configuration(Map<String, Object> conf) {
this(ConfigFactory.parseMap(conf));
}

/**
* Creates a new configuration by parsing a string in HOCON format.
*/
public Configuration(String s) {
this(ConfigFactory.parseString(s));
}

/**
* Creates a new configuration from a Scala-based configuration.
*/
Expand Down

0 comments on commit 22c115d

Please sign in to comment.