|
AssistedInject
an easier way to get Guice to build auto-wired factories.
AssistedInjectFactories are a well established pattern for creating value objects, model/domain objects (entities), or objects that combine parameterization and dependencies. Factories can be brittle and contain a lot of boilerplate. Guice can eliminate a lot of that boilerplate by auto-generating Factory implementations from simple interfaces. This process is (possibly misleadingly) known as assisted injection. Factories by HandSometimes a class 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;
@Inject
public RealPaymentFactory(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);
}
}...and a corresponding binding in the module: bind(PaymentFactory.class).to(RealPaymentFactory.class); It's annoying to write the boilerplate factory class each time this situation arises. It's also annoying to update the factories when the implementation class' dependencies change. Factories by AssistedInjectAssistedInject generates an implementation of the factory class automatically. To use it, annotate the implementation class' constructor and the fields that aren't known by the injector: public class RealPayment implements Payment {
@Inject
public RealPayment(
CreditService creditService,
AuthService authService,
@Assisted Date startDate,
@Assisted Money amount);
}
...
}Then bind a Provider<Factory> in the Guice module: AssistedInject in Guice 2.0bind(PaymentFactory.class).toProvider(
FactoryProvider.newFactory(PaymentFactory.class, RealPayment.class));AssistedInject in Guice 3.0Guice 3.0 improves AssistedInject by offering the ability to allow different factory methods to return different types or different constructors from one type. See the FactoryModuleBuilder javadoc for complete details. install(new FactoryModuleBuilder()
.implement(Payment.class, RealPayment.class)
.build(PaymentFactory.class));How & WhyAssistedInject 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 AssistedInject, it's easier to create classes that need extra arguments at construction time:
Inspecting AssistedInject Bindings (new in Guice 3.0)Visiting an assisted inject binding is useful for tests or debugging. AssistedInject uses the extensions SPI to let you learn more about the factory binding. You can visit bindings with an AssistedInjectTargetVisitor to see details about the binding. Binding<PaymentFactory> binding = injector.getBinding(PaymentFactory.class);
binding.acceptTargetVisitor(new Visitor());
class Visitor
extends DefaultBindingTargetVisitor<Object, Void>
implements AssistedInjectTargetVisitor<Object, Void> {
@Override void visit(AssistedInjectBinding<?> binding) {
// Loop over each method in the factory...
for(AssistedMethod method : binding.getAssistedMethods()) {
System.out.println("Non-assisted Dependencies: " + method.getDependencies()
+ ", Factory Method: " + method.getFactoryMethod()
+ ", Implementation Constructor: " + method.getImplementationConstructor()
+ ", Implementation Type: " + method.getImplementationType());
}
}
}
| |
Feature wishlist:
It would nice to have something on "Making parameter types distinct" here.
http://google-guice.googlecode.com/svn/trunk/latest-javadoc/com/google/inject/assistedinject/FactoryProvider.html
Making parameter types distinct The types of the factory method's parameters must be distinct. To use multiple parameters of the same type, use a named @Assisted annotation to disambiguate the parameters. The names must be applied to the factory method's parameters:
public interface PaymentFactory? {
...and to the concrete type's constructor parameters:
public class RealPayment? implements Payment {
...
Is it mistake? If it is constructor it must have name "RealPaymentFactory?" insted of "PaymentFactory?"
I agree with Leigh, please add a section dealing with parameters having the same type.
Agreed with Leigh. His comment saved hours that I have spent on trying to find where was the assisted injection mistake.
The construction on both Factory create and Concrete constructor did nicely the job: @Assisted("value1") type value1, @Assisted("value2") type value2, ...
Agreed with Leigh, too.
Also, how does one specify the values of the Assisted arguments?
This doc says: "AssistedInject in Guice 2.0"
But... http://google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/assistedinject/package-summary.html says:
"this extension requires guice-assistedinject-3.0.jar"
Is Assisted available / compatible with 2.0?
I ask because Apparently I need to use 2.0 with App Engine according to http://code.google.com/p/google-guice/wiki/GoogleAppEngine
Yes, AssistedInject is supported in 2.0. I think the reason why it mentions to use the 3.0 jar in that document is that you're reading the 3.0 documentation. However, if you notice, at the top of the page it says "AssistedInject ... Since: API Level Guice_2.0"
Quick question, what if I want to inject the RealPayment? class into another class? How do I do that?
@Garci, you don't inject RealPayment directly, you inject RealPaymentFactory and call the injectedRealPaymentFactory.create() method with the appropriate parameters populated. Or do you want two different ways to construct RealPayment, one with default values in place of the @Assisted parameters, and one with @Assisted parameters?