Sunday, January 1, 2017

Custom injection binding with Guice in Play

This post sequels the earlier one and explains, how to implement custom injection binding in Play.
You can read also this Practical guide to Guice, which explains how to work with Guice.

Custom injection binding in Play may be done with @ImplementedBy annotation or in a programmatic way with Guice module.

Injection with @ImplementedBy annotation


Injection with @ImplementedBy annotation is the simplest way.
The example below shows a service, which provides a facade to cache. The service is defined by an interface CacheProvider as following:
 @ImplementedBy(RunTimeCacheProvider.class)
 public interface CacheProvider {  
      CacheApi getCache();  
 }  
2. The service is implemented by a class RunTimeCacheProvider:
 public class RunTimeCacheProvider implements CacheProvider {  
      @Inject  
      private CacheApi appCache;  
      @Override  
      public public CacheApi getCache() {  
           return appCache;  
      }  
 }  
Note: the appCache data member is injected upon creation of a RunTimeCacheProvider instance.

3. Cache inspector is defined as a member of a controller code with @Inject annotation and is called from the controller:
 public class HomeController extends Controller {
      @Inject
      private CacheProvider cacheProvider;
      ...      
      public Result getCacheData() {
            Object cacheData = cacheProvider.getCache().get("DEMO-KEY");
            return ok(String.format("Cache content:%s", cacheData));  
      }      

Injection with @ImplementedBy annotation creates the fixed binding: CacheProvider in the above example is always instantiated with RunTimeCacheProvider. Such method fits only for a case, when there is an interface with a single implementation. It cannot help for an interface with several implementations or a class implemented as a singleton without abstract interface. Honestly speaking, @ImplementedBy will be used in rare cases if it all. It is more likely to use programmatic binding with Guice module.

Injection binding with a default Play module


The default Play module is a class named Module in the root project directory defined like this:
 import com.google.inject.AbstractModule;  
 public class Module extends AbstractModule {  
      @Override  
      protected void configure() {  
          // bindings are here           
      }  
 }  
Note: The snippet above shows binding inside configure, but of course any other binding method will be respected.

For programmatic binding of CacheProvider to RunTimeCacheProvider:
1. Remove @ImplementedBy annotation from the definition of CacheProvider:
 public interface CacheProvider {  
      CacheApi getCache();  
 }  
2. Implement Module configure as following:
 public class Module extends AbstractModule {  
      @Override  
      protected void configure() {  
        bind(CacheProvider.class).to(RunTimeCacheProvider.class);
      }  
 }  

Flexible injection binding with a default Play module


The RunTimeCacheProvider does not work well in JUnit tests with fake application (see the post about unit tests in Play). So, the different implementation of CacheProvider is demanded for unit tests. Injection binding should be done according to the environment. Let's see an example.

1. The class FakeCache provides a stub implementation of CacheApi to be used while running tests (its implementation is not such interesting - it is just a map).
2. The class FakeCacheProvider implements CacheProvider to be used while running tests:
 public class FakeCacheProvider implements CacheProvider {  
      private final CacheApi fakeCache = new FakeCache(); 
      @Override  
      public CacheApi getCache() {  
        return fakeCache;
      }  
 }  
2. Module is implemented as following:
 public class Module extends AbstractModule { 
      private final Environment environment;
      public Module(Environment environment, Configuration configuration) {
        this.environment = environment;
       }
      @Override  
      protected void configure() {  
         if (environment.isTest() ) {
          bind(CacheProvider.class).to(FakeCacheProvider.class);
        }
        else {
          bind(CacheProvider.class).to(RuntimeCacheProvider.class);   
        }
      }  
 }  
The example is good only for educational purpose. Binding for tests inside the module is not the best practice, since this couples between application and tests. Binding for tests should be done rather by tests itself and module should not be aware on test-specific implementation. The post Overriding Guice dependency injection for Play tests explains, how to do this better.

Injection binding with a custom module


A custom module is very similar to the default Play module. The difference is that it may have whatever name and belong to whatever package. For example, a module OnStartupModule belongs to the package modules.
 package modules;  
 import com.google.inject.AbstractModule;  
 public class OnStartupModule extends AbstractModule {  
      @Override  
      protected void configure() {  
           ...            
      }  
 }  
A custom module should be explicitly enabled for invocation by Play. For the module OnStartupModule the following should be added into application.conf:
play.modules.enabled += "modules.OnStartupModule"

Injecting a custom singleton class


Example of a custom singleton class injection is shown in the post Customizing of Json mapping in Play.

See working examples on Git.

No comments :

About the author

My Photo
I trust only simple code and believe that code should be handsome. This is not a matter of technology, but professional approach, consolidated after years of software development. I enjoy to cause things working and feel very happy, when I manage to solve a problem.
Back to Top