Little Guicers
I hate Guice. I hate all the DI frameworks, in fact. It's a lot of magic for very little benefit. Java 8 and beyond is perfectly capable of letting you model your system plumbing in pure Java using a pattern like Cake for Scala, or simply using (gasp) the new
keyword in a straight-up wiring method that looks a lot like Spring Java config, only more understandable and IDE/debug friendly.
But never mind that. I was recently called upon to do something like "partial" dependency injection using Guice, because I had the unique desire to
- produce multiple instances of a class
C
- which had a constructor that required several collaborators
- some of which were known at compile time, and some of which would vary for each instance
- and these instances needed to be AOP-Alliance-enabled proxies, so that interceptors would work on them
That last bit ruled out writing my own factories by hand which used new
, since they would never have the necessary enhancement by Guice.
Currying
The Google-verse sort of answered the question by mentioning FactoryModuleBuilder
. What the hell is that? Well, the easiest way for me to think about it is in terms of currying in functional languages.
> val f = ( x : int, y : int, z : int ) => x * y + z f : int * int * int => int > val curryF = ( x : int ) => ( y : int ) => ( z : int ) => f( x, y, z ) curryF : int => int => int => int > val someF = curryF( 2 ) someF : int => int => int > val moreF = someF( 3 ) moreF : int => int > val answer = moreF( 4 ) answer : int = 10
Factories in Guice
Factories in OO languages are basically curried, partially evaluated constructors. (Builders are like extreme versions of factories where the parameters are named.) In normal instantiation you do new Thing( x, y, z )
and with a factory you do new ThingFactory( x ).make( y, z )
. My requirements above meant that I needed Guice to give me a little Guice factory for instances which was like a partially-evaluated injector: I knew x
at compile-time and had bound it up with Guice already, but y
and z
would be unique to each Thing
.
The code
Binding a static Thing
would look like this:
public class Thing { @Inject public Thing( X x, Y y, Z z ) { /* ... */ } } public class ThingModule extends AbstractModule { @Override public void configure() { bind( X.class ).toInstance( x ); bind( Y.class ).toInstance( y ); bind( Z.class ).toInstance( z ); bind( Thing.class ); } } // Guice-bound Thing Guice.createInjector( new ThingModule() ).getInstance( Thing.class );
The factory way looks like this (notice the @Assisted
annotations):
public interface ThingFactory { Thing make( Y y, Z z ); } public class Thing { @Inject public Thing( X x, @Assisted Y y, @Assisted Z z ) { /* ... */ } } public class ThingModule extends AbstractModule { @Override public void configure() { bind( X.class ).toInstance( x ); install( new FactoryModuleBuilder().build( ThingFactory.class ) ); } } // Guice-bound ThingFactory creates parameterized, yet Guice-bound, Thing // I guess we're "assisting" Guice by providing y and z on our own, hence the name Guice.createInjector( new ThingModule() ).getInstance( ThingFactory.class ).make( y, z );
Of course the main way to use this construction is to inject the ThingFactory
itself into some other object that needs dynamic Thing
s.
The amazing thing is that if any method of Thing
happens to match an interceptor, the resulting instance will be an AOP-alliance proxy and the interceptors will work. A hand-built factory using new
, by contrast, would produce plain Thing
instances that would be oblivious to interceptors.