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

2559 views
Skip to first unread message

limpb...@gmail.com

unread,
Jun 15, 2007, 11:58:26 PM6/15/07
to google-guice
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: http://publicobject.com/publicobject/assistedinject/assistedinject_20070615.zip
Javadocs: http://publicobject.com/publicobject/assistedinject/javadocs/index.html

Robbie Vanbrabant

unread,
Jun 16, 2007, 8:14:07 AM6/16/07
to google-guice
Looks like a useful addition. My questions:
- Why use @AssistedInject and not @Inject? The less syntax the better.
- Why limit this to constructors? I'm guessing because you would use
the Builder pattern for optional dependencies.
- Maybe there's a better name for "Assisted". "Provided" or something
like that?

I think this feature would really shine if you could support factories
that can create multiple related objects:
bind(FooFactory.class)
.toProvider(FactoryProvider.newFactory(BankFactory.class,
RealPayment.class, Loan.class, Insurance.class, /* more... */));

Thanks,
Robbie

> Source with Jar:http://publicobject.com/publicobject/assistedinject/assistedinject_20...
> Javadocs:http://publicobject.com/publicobject/assistedinject/javadocs/index.html

Bob Lee

unread,
Jun 16, 2007, 1:50:45 PM6/16/07
to google...@googlegroups.com
This is cool. You could use compile time code generation to generate the interface, too, i.e. apt or something.

Bob

limpb...@gmail.com

unread,
Jun 16, 2007, 2:13:55 PM6/16/07
to google-guice
On Jun 16, 5:14 am, Robbie Vanbrabant <robbie.vanbrab...@gmail.com>
wrote:

> - Why use @AssistedInject and not @Inject? The less syntax the better.
We chose to go with @AssistedInject for readability. The
implementation and factory might be in completely different places
in the source tree, so this tips off the reader that the injector
alone
cannot build this class.

> - Why limit this to constructors?

Probably just 'cause we tend to prefer immutable objects that
set all their fields at construction time. Our code will get Guice
to do any necessary field and method injection if necessary, but
@Assisted is for constructors only.

> - Maybe there's a better name for "Assisted". "Provided" or something
> like that?

@Provided is weird because there's already a Provider interface that
means something different. If you can come up with other good names,
I'm interested. What we're really looking for is a concise way to
describe
that the object is being created by both the injector and by some
client
who needs an instance.

> I think this feature would really shine if you could support factories
> that can create multiple related objects:

That would probably work okay. One wrinkle is if the factory
methods return types don't cleanly map to a single implementation
class. Consider a Factory that builds Java collections classes:
interface CollectionFactory {
<T> Set<T> createSet(T... elements);
<T extends Comparable> SortedSet<T> createSortedSet(T...
elements);
}
If we'd like to use this with HashSet and TreeSet as our
respective implementations, then it won't know which
implementation to return for the createSet() method.

One thing we already do is support overloaded factory methods.
If you have say, createPayment(Date, Money) and also
createPayment(Money), then it looks for 2 constructors
annotated as @AssistedInject.

Thanks for the feedback Robbie. If you try it out and run into
any problems, let me know and we'll write a fix!

Cheers,
Jesse

Robbie Vanbrabant

unread,
Jun 17, 2007, 4:50:25 AM6/17/07
to google-guice

On Jun 16, 8:13 pm, "j...@swank.ca" <limpbiz...@gmail.com> wrote:
...


> @Provided is weird because there's already a Provider interface that
> means something different. If you can come up with other good names,
> I'm interested.

Some I can think of right now:
Inject: @PartialInject, @HybridInject, @FactoryInject, @InjectSome,
@InjectKnown, ...
Parameters: @Manufactured, @Runtime, @Unknown, @DontInject, @DIY,
@Later, ...

> > I think this feature would really shine if you could support factories
> > that can create multiple related objects:
>
> That would probably work okay. One wrinkle is if the factory
> methods return types don't cleanly map to a single implementation
> class.

Interesting problem, indeed. Maybe you can do something like a return
type binding.
ReturnTypeBinding rtb =
bindReturnType(Payment.class).to(RealPayment.class);

bind(PaymentFactory.class).toProvider(FactoryProvider.newFactory(PaymentFactory.class,
rtb));

> Thanks for the feedback Robbie. If you try it out and run into
> any problems, let me know and we'll write a fix!

Will do!

> Cheers,
> Jesse

Thanks,
Robbie

jmou...@gmail.com

unread,
Jun 17, 2007, 6:18:17 AM6/17/07
to google-guice
Hi Robbie,

Thanks for the suggestions. I like the idea of the both the
constructor annotation and the parameter annotation being similar.
Using one of your examples, maybe something like @FactoryInject and
@FromFactory. I think this might help unfamiliar users realize that
they are related.

As for you second suggestion, what about creating the single-product
factories as normal, then creating a super-factory:

bind(AppleFactory.class).toProvider(FactoryProvider.newFactory(AppleFactory.class,
GoldenDelicious.class));
bind(OrangeFactory.class).toProvider(FactoryProvider.newFactory(OrangeFactory.class,
Tangerine.class));

bind(FruitFactory.class).toProvider(FactoryProvider.combineFactories(AppleFactory.class,
GoldenDelicious.class));

You'd have to write the FruitFactory interface yourself (although it
just be a combination of component factories). The advantage would be
that we wouldn't need to make any changes to Guice itself.

What's the motivation of this combined approach? Having to only
inject one factory instead of several? Does this come up often?

Jerome

On Jun 17, 1:50 am, Robbie Vanbrabant <robbie.vanbrab...@gmail.com>
wrote:

Robbie Vanbrabant

unread,
Jun 17, 2007, 9:01:25 AM6/17/07
to google-guice
I particularly like the FactoryInject and Manufactured combination.

Let me clarify why I proposed the combined Factory thing: In the end,
this factory generation feature is all about saving you from typing
and maintaining some code. To make sure that your boilerplate code
(configuration, interfaces) doesn't outgrow the original issue, I
thought it would be useful to group related objects, as you would if
you were not using Guice at all. Because if you do have related
objects, chances are high that they need the same kind of factory
assistance, and that they will be used together. In theory -- so hold
your horses. I just thought is was worth discussing.

Robbie

Bob Lee

unread,
Jun 17, 2007, 12:05:43 PM6/17/07
to google...@googlegroups.com
Just to throw out another idea:

 @InjectWithParameters

 public RealPayment(
       CreditService creditService,
       AuthService authService,
       @Parameter Date startDate,
       @Parameter Money amount);
 }

Bob

Dhanji R. Prasanna

unread,
Jun 18, 2007, 10:06:59 AM6/18/07
to google...@googlegroups.com
@Inject(explicitOnly = true)
Reply all
Reply to author
Forward
0 new messages