Choosing Between Implementations
The chef was obviously not pleased to figure out that the FortuneServiceImpl only had two fortunes to offer. To get some more variation in the messages, our chef subscribes to a second service, the MegaFortuneService, shown in Listing 6. Because the original subscription doesn't end until the end of the year, some way to choose between the two is necessary.
public class MegaFortuneService implements FortuneService { private static final List<FortuneService> SERVICES = Arrays.<FortuneService>asList( new FunnyFortuneService(), new QuoteFortuneService() ); public String randomFortune() { int index = new Random().nextInt(SERVICES.size()); return SERVICES.get(index).randomFortune(); } }
Previously, the Guice knew which FortuneService to inject for Chef, because we had a binding in a Module implementation (see Listing 2). Common sense tells me to just add another binding for MegaFortuneService. Listing 7 shows what I came up with.
public class CommonSenseModule extends AbstractModule { protected void configure() { bind(FortuneService.class).to(FortuneServiceImpl.class); bind(FortuneService.class).to(MegaFortuneService.class); } }
You can easily modify the code in Listing 4 to create the Injector with the Module from Listing 7. However, when you do, you'll notice that Guice blows up at start-up. You'd see something like Listing 8.
Exception in thread "main" com.google.inject.CreationException: Guice configuration errors: 1) Error at chapter2.CommonSenseModule.configure(CommonSenseModule.java:12): A binding to chapter1.FortuneService was already configured at chapter2.CommonSenseModule.configure(CommonSenseModule.java:11).
Oh man, Guice doesn't like that. What did I forget? If you take a second look at the Chef constructor, shown in Listing 9, you'll see. Note: Guice's exceptions comprise a feature on their own. They go the extra mile and present you a human readable message and line numbers from your configuration code where appropriate. You'll be pleasantly surprised.
@Inject public Chef(FortuneService fortuneService) { this.fortuneService = fortuneService; }
Of course! Guice can't tell which FortuneService we need for Chef! So even if Guice would allow our CommonSenseModule bindings, using them would have never worked, because Guice has two FortuneService instances to choose from. I need some way to tell Guice, "Give me the MegaFortuneService!" without having to use the concrete implementations directly. Instead of letting you write more configuration, Guice solves this problem elegantly using binding annotations. Listing 10 provides an example.
@Inject public Chef(@Mega FortuneService fortuneService) { this.fortuneService = fortuneService; }
Now I can tell Guice which service is which in a module, as shown in Listing 11.
public class BindingAnnotationModule extends AbstractModule { protected void configure() { bind(FortuneService.class).to(FortuneServiceImpl.class); bind(FortuneService.class) .annotatedWith(Mega.class) .to(MegaFortuneService.class); } }
Again, my configuration remains highly readable, "Bind all requests for FortuneService annotated with @Mega to MegaFortuneService." One might hope that Guice provides these binding annotations for you. Alas, that wouldn't work for the same reason actors shouldn't go into politics: sometimes, you just have absolutely no idea what you're talking about. Similarly, Guice can't possibly know how to name your binding annotations, what their visibility should be, or where you would want to place them (in a constructor, method, or so on). That's why you need to create them yourself, much like in the @Mega example in Listing 12. Most binding annotations look very similar, so don't feel bad copying and pasting the boilerplate code. In fact, I even recommend doing so, because that means you're less likely to forget something.
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.PARAMETER}) @BindingAnnotation public @interface Mega {}
Scary stuff.
To support this binding annotation functionality, Guice internally identifies all bindings with an instance of the Key class. A Key instance is a combination of a type (FortuneService) and an optional annotation type (@Mega). If you played around with the Injector a bit in the previous section, you probably noticed that the getInstance(...) method has an overload that takes a Key object instead of a Class one. There's only one Chef, but if you would like to get a MegaFortuneService directly, you would do so as in Listing 13.
injector.getInstance(Key.get(FortuneService.class, Mega.class));
If you call getInstance(...) with a Class, for example, Chef.class, Guice actually delegates to getInstance(Key) internally. So the two lines in Listing 14 are essentially the same.
injector.getInstance(Chef.class); injector.getInstance(Key.get(Chef.class));
Understanding that every single binding is internally represented by a Key object, including simple class bindings like Chef, will save you lots of time. Especially when you start using Guice's object lifetime support (scopes). I can't emphasize this enough, so repeat it after me: Every binding is represented by a Key, the whole Key and nothing but the Key, so help me Bob.