Atom Feed SITE FEED   ADD TO GOOGLE READER

AssistedInject, an easier way to help the Guice Injector build objects

Frequently on my team we have a class that gets some of its constructor parameters from the Guice Injector and others from the caller:
public class RealPayment implements Payment {
public RealPayment(
CreditService creditService, // from the Injector
AuthService authService, // from the Injector
Date startDate, // from the instance's creator
Money amount); // from the instance's creator
}
...
}

The standard solution to this problem is to write a factory that helps Guice build the objects:
public interface PaymentFactory {
public Payment create(Date startDate, Money amount);
}

public class RealPaymentFactory implements PaymentFactory {
private final Provider<CreditService> creditServiceProvider;
private final Provider<AuthService> authServiceProvider;
public PaymentFactory(Provider<CreditService> creditServiceProvider,
Provider<AuthService> authServiceProvider) {
this.creditServiceProvider = creditServiceProvider;
this.authServiceProvider = authServiceProvider;
}
public Payment create(Date startDate, Money amount) {
return new RealPayment(creditServiceProvider.get(),
authServiceProvider.get(), startDate, amount);
}
}

Then in our module:
   bind(PaymentFactory.class).to(RealPaymentFactory.class);

We think it's annoying to have to write the boilerplate factory class each time we're in this situation. It's also annoying to have to update the factories whenever our implementation class' dependencies change.

This motivated Jerome Mourits and I to write a Guice extension called AssistedInject. It generates the implementation of our Factory class automatically. To use it, we annotate the implementation class' constructor and the fields that aren't known by the Injector:
public class RealPayment implements Payment {
@AssistedInject
public RealPayment(
CreditService creditService,
AuthService authService,
@Assisted Date startDate,
@Assisted Money amount);
}
...
}

Then we reflectively create a Provider<Factory> in the Guice module:
bind(PaymentFactory.class).toProvider(
FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));

Our FactoryProvider class maps the create() method's parameters to the corresponding @Assisted parameters in the implementation class' constructor. For the other constructor arguments, it asks the regular Injector to provide values.

With the FactoryProvider class, we can easily to create classes that need extra arguments at construction time:

  1. Annotate the constructor and assisted parameters on the implementation class (such as RealPayment.java)
  2. Create a Factory interface with a create() method that takes only the assisted parameters. Make sure they're in the same order as in the constructor
  3. Bind that FactoryInterface to our FactoryProvider interface.


The code is available as an open source Guice extension:
  • Source with Jar
  • Javadocs