Just-in-time Providers for Guice

May 31, 2010

Guice is widely recognised as the best dependency injection framework for the JVM world and specifically for Java. It has brought together ideas from numerous projects, including Spring and PicoContainer, and popularised type driven development. Dependency injection is one of the principal enablers for test driven development, and as such central to our software practices here at kaChing.

Making Guice more Dynamic

However, we often encounter repetitive bindings in our code base which could be simplified by using run time information and dynamic decision making. Consider the following binding.

@Inject EntityMarshaller<User> userMarshaller;

Here, we inject a marshaller for the specific type User. In order to make Guice recognise this binding, we add a bit of code to our module.

bind(new TypeLiteral<EntityMarshaller<User>>() {})
    .toInstance(JsonMarshaller.createEntityMarshaller(User.class));

But what if we now want a marshaller for another type, say a Portfolio? You’ll need to add another binding just like the one highlighted previously. This is both tedious and contrary to the DRY principle!

Instead of duplicating the binding, we want to dynamically provide this binding at run time in the very same way Guice implements its default behaviour of using a constructors annotated with @Inject.

Introducing JitProvider

A just-in-time provider, or JitProvider, accomplishes just that. You say what keys you can provide an instance for, and for those keys provide an instance. It’s just that simple.

public interface JitProvider<T> {
  boolean canProvide(Key<?> key);
  T get(Key<T> key);
}

In line with other bindings, just-in-time providers integrate in a natural way.

void configure() {
  bindJitProvider(MyJitProvider.class).in(Scopes.SINGLETON);
}

Or directly annotating classes.

@Singleton @ProvidedJustInTime(MyJitProvider.class)
class MyGenericFactory<T> { ...

 

Using this Patch

The code for just-in-time providers is available on github as a fork of Guice. In addition, we’ve prepared to two easy ways for you to use this patch. It goes without saying that this patch is already in production on multiple of our servers.

You can download a compiled snapshot guice-snapshot-3e67fc2b0bc14140dcb3.jar and use this jar in your project.

Otherwise, download the patch file and apply it to a clean checkout of Guice as follows.

svn checkout http://google-guice.googlecode.com/svn/trunk/ google-guice-clean
git apply patch.jitproviders

(The patch was created using git diff c88196e278d51f499df9 3e67fc2b0bc14140dcb3 > patch.jitproviders.)

Help us Make Just-in-time Providers Mainstream

We will lobby Guice authors to adopt this new feature for the 3.0 release. Help us make this a reality by giving us feedback and letting us know how you use just-in-time providers!

Full Example: Using Just-in-time Providers for the JsonMarshaller

The example below will print {"name":"Pascal-Louis Perez"}. You’ll need four jars to make it compile: json-0.20.jar, guice-snapshot-3e67fc2b0bc14140dcb3.jar, aopalliance.jar and javax.inject.jar.

import static com.google.inject.Scopes.SINGLETON;

import java.lang.reflect.ParameterizedType;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.JitProvider;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.twolattes.json.Entity;
import com.twolattes.json.EntityMarshaller;
import com.twolattes.json.Marshaller;
import com.twolattes.json.TwoLattes;
import com.twolattes.json.Value;

public class UsingJitProviders {

 @Inject
 UsingJitProviders(Marshaller<User> marshaller) {
  System.out.println(marshaller.marshall(new User()));
 }
 
 @Entity
 static class User {
  @Value String name = "Pascal-Louis Perez";
 }
 
 public static void main(String[] args) {
  Guice.createInjector(new AbstractModule() {
   @Override
   protected void configure() {
    bindJitProvider(new JitProvider<EntityMarshaller<?>>() {
     public boolean canProvide(Key<?> key) {
      if (Marshaller.class.isAssignableFrom(key.getTypeLiteral().getRawType())) {
       if (key.getTypeLiteral().getType() instanceof ParameterizedType) {
        ParameterizedType marshallerType = (ParameterizedType) key.getTypeLiteral().getType();
        if (marshallerType.getActualTypeArguments()[0] instanceof Class<?>) {
         Class<?> entityClass = (Class<?>) marshallerType.getActualTypeArguments()[0];
         return entityClass.getAnnotation(Entity.class) != null;
        }
       }
      }
      return false;
     }
     public EntityMarshaller<?> get(Key<?> key) {
      ParameterizedType marshallerType = (ParameterizedType) key.getTypeLiteral().getType();
      Class<?> entityClass = (Class<?>) marshallerType.getActualTypeArguments()[0];
      return TwoLattes.createEntityMarshaller(entityClass);
     }
    }).in(SINGLETON);
   }
  }).getInstance(UsingJitProviders.class);
 }

}