Guice
Links
- Video Google I/O 2009 Big Modular Java with Guice
- Google Guice Dependency Injection Example Tutorial
- Google Guice Wiki
- Google Guice Motivation
- Google Guice Getting Started
- Google Guice Bindings
- Google Guice Injections
- Google Guice User Guide
Motivation
You want to do something, e.g. do the payment for a shop.
// do payment
}
Maybe you need different variants of this. So having an interface and several implementations:
public boolean pay(Integer amount, String recipient);
}
@Override
public boolean pay(Integer amount, String recipient) {
System.out.println("Pay with Visa "+amount+" to recipient");
return true;
}
}
And a client, e.g. a shop code will use this like this
payment.pay(42, "John Doe");
The has some problems. If you want to replace the payment you have to search the full code for any place creating a payment, plus the code is hard to test without paying accidentally.
Standard solution would be to provide a factory for payments
public static PaymentInterface getPayment() {
return new PaymentVisa();
}
}
And the client uses it like this
Now you can switch die Payment implementation at one place, and the factory may return different implementations for tests as well.
Google Guice Dependency Injection
Different approach, Dependency Injection. The client gets its dependencies from the outside.
private PaymentInterface payment;
public MyShop(PaymentInterface pPayment) {
payment=pPayment;
}
public void goShopping() {
payment.pay(42, "John Doe");
}
}
And the dependencies are provided when your client is created
With Guice the is even more elegant. This is how to do Add this to your Maven project to get Guice
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
</dependency>
Now create a class which inherits from AbstractModule and in its configure method choose which implementation should be chosen every time the class PaymentInterface is required Google Guice LinkedBindings
public class PaymentModule extends AbstractModule {
@Override
protected void configure() {
bind(PaymentInterface.class).to(PaymentVisa.class);
}
}
In our client we add an annotation to the constructor that we need somebody to inject our dependencies
public class MyShop {
private PaymentInterface payment;
@Inject
public MyShop(PaymentInterface pPayment) {
payment=pPayment;
}
public void goShopping() {
payment.pay(42, "John Doe");
}
}
In order to make Guice inject your dependencies for you, let Guice create your Client for you
MyShop shop = injector.getInstance(MyShop.class);
shop.goShopping();
Now if you want to do a unit test, add a mock Payment method
public boolean pay(Integer amount, String recipient) {
System.out.println("We do NOT pay "+amount+" to recipient "+recipient+" as we are testing");
return true;
}
}
Within your unit tests (e.g. in the @Before method) get a special injector with a special Module class which binds the payment to the mock payment.
@Override
protected void configure() {
bind(PaymentInterface.class).to(PaymentMock.class);
}
});
So the unit tests get their instances like this
shopTest.goShopping();
Sometimes you want to be able to have more than one implementation of an interface available. You can either use self created annotations to mark with injection should be using which type
Google Guice BindingAnnotations
@Inject
public MyShop(
@PayWithVisa PaymentInterface pPayment1,
@PayWithMastercard PaymentInterface pPayment2,
@PayMock PaymentInterface pPayment3) {
payment1=pPayment1;
payment2=pPayment2;
payment3=pPayment3;
}
and
public class PaymentModule extends AbstractModule {
@Override
protected void configure() {
// bind(PaymentInterface.class).to(PaymentVisa.class);
bind(PaymentInterface.class).annotatedWith(PayWithVisa.class). to(PaymentVisa.class);
bind(PaymentInterface.class).annotatedWith(PayWithMastercard.class).to(PaymentMastercard.class);
bind(PaymentInterface.class).annotatedWith(PayMock.class). to(PaymentMock.class);
}
}
Downside of this is that you have to create all required annotations with this code
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
@BindingAnnotation @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
public @interface PayWithVisa {}
You can use the provided
instead and bind with
But you should prefer to create your own annotations.
Multibinding Imagine you have a method that accepts several payment implementations
public PaymentCaller(Set<PaymentInterface> supportedPayments) {
}
And you want to bind some payment implementations to that. In your module class do this
paymentbinder.addBinding().to(PaymentVisa.class);
paymentbinder.addBinding().to(PaymentMasterCard.class);
If you want to control how the implementations are created (e.g. because you need to do something to them before they can be used), you can have @Provides methods in the Module class. The return type of the @Provides method decide which one to use
@Override
protected void configure() {
//bind(PaymentInterface.class).to(PaymentVisa.class);
bind(PaymentInterface.class).to(PaymentMastercard.class);
}
@Provides
PaymentVisa providePaymentVisa() {
PaymentVisa visa = new PaymentVisa();
visa.setVisaCode(42);
return visa;
}
@Provides
PaymentMastercard providePaymentMastercard() {
PaymentMastercard master = new PaymentMastercard();
master.setMasterCardInit("abcd");
return master;
}
}
You can even manually control what type is returned. This method has the Provides annotation so when MyType needs to be injected that method will be called. The method itself gets two possible instances of MyType injected. Provider<Foo> makes that Injection lazy, so only if you try to get it, it is actually created. In the method you choose the instance you need.
static MyType getMyType(@Named("mySwitch") final boolean mySwitch,
final Provider<Bar> bar, final Provider<Foo> foo) {
MyType result;
if (mySwitch) {
result=bar.get();
} else {
result=foo.get();
}
return result;
}
Google Guice ProviderBindings If creating the implementations gets more complex and you want to hide it somewhere, or if create them is expensive and you only want to do it if the implementation is really needed (lazy load) go for Provider. Create a special class which will create the implementation only once its get method is called
public class VisaPaymentProvider implements Provider<PaymentInterface> {
public PaymentInterface get() {
PaymentVisa result = new PaymentVisa();
result.setVisaCode(42);
return result;
}
}
In the module bind to the provider instead of the implemenation
bind(PaymentInterface.class).toProvider(VisaPaymentProvider.class);
//bind(PaymentInterface.class).toProvider(VisaPaymentProvider.class).in(Singleton.class);
This will no longer create the implemenation, only the Provider
You can also bind from within a module fixed values to variables.
bindConstant().annotatedWith(DBPassword.class).to("123456");
And within the class
private String dbUser;
@Inject @DBPassword
private String dbPassword;
Use Injector to inject named constant
Overwrite a Guice binding, useful for JUnit tests
Create a module that has the desired binding in its configure
@Override
protected void configure() {
bind(String.class).annotatedWith(Names.named("dbName")).toInstance("test-db");
}
}
Add this module and maybe others to an array, e.g. overrideModules. Get the normal Guice modules and get the injector like this
Inject depending on a constant
Usually you bind you implementation to and Interface via bind statements. You can also have an configuration constant (inject via @Named) and then inject something different, depending on its value. This is for example nice if one constant should change the injection of several different things
@Override
protected void configure() {
final MapBinder<String, MyInterface> map = MapBinder
.newMapBinder(binder(), String.class, MyInterface.class);
map.addBinding("foo").to(MyImplementation1.class);
map.addBinding("bar").to(MyImplementation2.class);
}
@Provides
public MyInterface selectMyInterface(final Map<String, MyInterface> map,
@Named("your.fav.implementation") final String choose) {
return map.get(choose);
}
}