Guice (pronounced “juice”) is an ultra-lightweight, next-generation dependency injection container for Java 5 and later.
Guice alleviates the need for factories and the use of new in your Java code. Think of Guice’s @Inject as the new new. You will still need to write factories in some cases, but your code will not depend directly on them. Your code will be easier to change, unit test and reuse in other contexts.
Creating a simple DI application
//Service Receiver.
public class Chef {
private final FortuneService fortuneService;
@Inject
public Chef(FortuneService fortuneService) {
this.fortuneService = fortuneService;
}
public void makeFortuneCookie() {
new FortuneCookie(fortuneService.randomFortune());
}
}
//Service Provider -- Implementation 1
public class FortuneServiceImpl implements FortuneService {
private static final List<string> MESSAGES =
Arrays.asList(
"Today you will have some refreshing juice.",
"Larry just bought your company."
);
public String randomFortune() {
return MESSAGES.get(new Random().nextInt(MESSAGES.size()));
}
}
//Configuration Module
public class ChefModule implements Module { //can 'implements Module' or 'extends AbstractModule'
public void configure(Binder binder) {
binder.bind(FortuneService.class)
.to(FortuneServiceImpl.class)
.in(Scopes.SINGLETON);
}
}
//Bootstraping
public class FortuneApplication {
public static void main(String[] args) {
Injector i = Guice.createInjector(new ChefModule()); // Can also be Injector i = Guice.createInjector(Stage.PRODUCTION, new ChefModule(),...); i.e 1 or more modules
Chef chef = i.getInstance(Chef.class); //which is same as i.getInstance(Key.get(Chef.class));
chef.makeFortuneCookie();
}
}
Here is the sequence diagram of the above code snippet at runtime.
| Main | FortuneApplication |
| Guice |
Guice.createInjector(new ChefModule()); |
| MyModule | ChefModule |
| Binder |
ChefModule.configure(Binder binder); |
| Injector | @Inject Annotation |
The following is the Guice Runtime Model
Note: The above code snippet uses Key.Type. It does not use any annotation
DI with annotations — ‘choosing between implementations’
Using the above Interface (i.e FortuneService) & Implemementation (i.e FortuneServiceImpl)
//Service Provider -- Implementation 2
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();
}
}
//Configuration Module
public class CommonSenseModule extends AbstractModule {
protected void configure() {
bind(FortuneService.class).to(FortuneServiceImpl.class);
bind(FortuneService.class).to(MegaFortuneService.class); //ERROR!!!
}
}
Hence, we need a better means to tell Guice to use MegaFortuneService. Using 'Binding Annotations' would resolve this problem.
//Service Receiver.
public class Chef {
private final FortuneService fortuneService;
@Inject
public Chef(@Mega FortuneService fortuneService) { //Updated Usage!
this.fortuneService = fortuneService;
}
public String randomFortune() {
return MESSAGES.get(new Random().nextInt(MESSAGES.size()));
}
}
//Module class.
public class ChefModule extends AbstractModule {
protected void configure() {
bind(FortuneService.class).to(FortuneServiceImpl.class);
bind(FortuneService.class)
.annotatedWith(Mega.class) //Add annotation key!
.to(MegaFortuneService.class);
}
}
//Annotation class.
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@BindingAnnotation
public @interface Mega {}
//Bootstraping
public class FortuneApplication { //Main class
public static void main(String[] args) {
Injector i = Guice.createInjector(new ChefModule()); // Can also be Injector i = Guice.createInjector(Stage.PRODUCTION, new ChefModule());
Chef chef = i.getInstance(Key.get(FortuneService.class, Mega.class)); //Refer: http://docs.google.com/File?id=dd2fhx4z_30f3dppc
chef.makeFortuneCookie();
}
}
Implicit Binding – @ImplementedBy
//Service Provider
@ImplementedBy(FortuneServiceImpl.class) //Implicit binding. Note: Module configuration i.e bind(FortuneService.class).to(FortuneServiceImpl.class) would still take precidence over this.
public interface FortuneService {
String randomFortune();
}
//Service Receiver.
public class Chef {
private final FortuneService fortuneService;
@Inject
public Chef(FortuneService fortuneService) {
this.fortuneService = fortuneService;
}
public void makeFortuneCookie() {
new FortuneCookie(fortuneService.randomFortune());
}
}
//Service Provider -- Implementation 1
public class FortuneServiceImpl implements FortuneService {
private static final List<string> MESSAGES =
Arrays.asList(
"Today you will have some refreshing juice.",
"Larry just bought your company."
);
public String randomFortune() {
return MESSAGES.get(new Random().nextInt(MESSAGES.size()));
}
}
Notice that, 'ChefModule' is not required for default configuration.
</string></pre>
<span style=";font-family:verdana;font-size:130%;" >Scoping</span>
[sourcecode language='java']
public class ChefModule extends AbstractModule {
protected void configure() {
bind(FortuneService.class)
.to(FortuneServiceImpl.class)
.in(Singleton.class); //Either Singleton.class or Scopes.SINGLETON
bind(FortuneService.class)
.annotatedWith(Mega.class)
.to(MegaFortuneService.class)
.asEagerSingleton(); //Do singletons load lazily or eagerly?
}
}
Guice Provider
A Provider implementation is basically a small factory class that Guice will invoke whenever it needs an instance of the given type.
public class Gum {}
public class GumballMachine {
@Inject
private Provider<gum> gumProvider;
public Gum dispense() {
return gumProvider.get();
}
}
public class GumProvider implements Provider<gum> {
public Gum get() {
return new Gum();
}
}
public class GumModule extends AbstractModule {
protected void configure() {
bind(Gum.class).toProvider(GumProvider.class);
}
}
public class GumballExample {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new GumModule());
GumballMachine m = injector.getInstance(GumballMachine.class);
System.out.println(m.dispense()); // Gum@10f11b8
System.out.println(m.dispense()); // Gum@544ec1
}
}
Injecting into Provider:
public class BlueGumProvider implements Provider<gum> {
@Inject Color color;
public Gum get() {
return new Gum(color);
}
}
Implict Binding – @ProvidedBy
Similar to @ImplementedBy</pre>
<span style=";font-family:verdana;font-size:130%;" >Using @Named</span>
[sourcecode language='java']
@Named is a OOTB annotation, that uses string identifier to differentiate among different bindings.
//Service Consumer
public class ActionMovie {
@Inject @Named("stallone")
private Actor actor;
}
Module code snippet:
bind(Actor.class).annotatedWith(Names.named("stallone")).to(...); //Note: The usual way of using annotations, 'bind(Actor.class).annotatedWith(Named.class).to(...);' will NOT work.
</pre>
<span style=";font-family:verdana;font-size:130%;" >Binding Constants</span>
[sourcecode language='java']
//Service Consumer
public class ConcertHall {
@Inject @Named("capacity")
private int capacity;
@Inject @Named("stage")
private Class stageType;
@Inject @Named("setting")
private Setting setting;
public String toString() {
return String.format("%s[capacity=%s, stageType=%s, setting=%s]",
getClass().getName(), capacity, stageType, setting);
}
}
//Another service provider with package "somepackage"
public class BigStage {}
//Service Configuration
public class ConcertModule extends AbstractModule {
protected void configure() {
bindConstant()
.annotatedWith(Names.named("capacity"))
.to("322");
bindConstant()
.annotatedWith(Names.named("stage"))
.to("somepackage.BigStage");
bindConstant()
.annotatedWith(Names.named("setting"))
.to("INDOOR");
}
}
//Bootstraping
public class ConcertExample {
public static void main(String[] args) {
Injector i = Guice.createInjector(new ConcertModule());
ConcertHall hall = i.getInstance(ConcertHall.class);
System.out.println(hall); //Outputs, ConcertHall[capacity=322, stageType=class somepackage.BigStage, setting=INDOOR]
}
}
A better way of binding, especially while using generic types.
//Service Consumer
public class LongHolder {
@Inject @Named("long")
@Inject @Named("list") List<string> strings;
@Inject @Named("list") List<integer> integers;
private Long theLong;
}
//Service Configuration
public class LongModule extends AbstractModule {
protected void configure() {
bind(Long.class)
.annotatedWith(Names.named("long"))
.toInstance(123L);
bind(new TypeLiteral<list><string>>(){})
.annotatedWith(Names.named("list"))
.to(new TypeLiteral<arraylist><string>>(){});
bind(new TypeLiteral<list><integer>>(){})
.annotatedWith(Names.named("list"))
.to(new TypeLiteral<arraylist><integer>>(){});
}
}
//Bootstraping
public class LongHolderExample {
public static void main(String[] args) {
Injector i = Guice.createInjector(new LongModule());
i.getInstance(LongHolder.class);
}
}
//A more readable Service Configuration that can deal with types. --- Essentially the same functionality.
public class TypeLiteralModule extends AbstractModule {
protected void configure() {
bind(listOf(String.class))
.annotatedWith(Names.named("list"))
.to(arrayListOf(String.class));
bind(listOf(Integer.class))
.annotatedWith(Names.named("list"))
.to(arrayListOf(Integer.class));
}
@SuppressWarnings("unchecked")
static <t> TypeLiteral<list><t>> listOf(final Class<t> parameterType) {
return (TypeLiteral<list><t>>) TypeLiteral.get(new ParameterizedType() {
public Type[] getActualTypeArguments(){return new Type[] {parameterType};}
public Type getRawType() { return List.class; }
public Type getOwnerType() { return null; }
}
);
}
@SuppressWarnings("unchecked")
static <t> TypeLiteral<arraylist><t>> arrayListOf(final Class<t> parameterType) {
return (TypeLiteral<arraylist><t>>) TypeLiteral.get(new ParameterizedType() {
public Type[] getActualTypeArguments(){return new Type[] {parameterType};}
public Type getRawType() { return ArrayList.class; }
}
);
}
}
Properties
Sample property file: db.properties
db.url = jdbc:mysql://localhost/test
db.driver = com.mysql.jdbc.Driver
db.user = test
db.password = test
public class PropertiesModule extends AbstractModule {
protected void configure() {
try {
Properties databaseProperties = loadProperties("db.properties");
Names.bindProperties(binder(), databaseProperties);
} catch (RuntimeException e) {
addError("Could not configure database properties", e);
}
}
private static Properties loadProperties(String name) { //loads file as java.util.Properties
Properties properties = new Properties();
InputStream is = new Object(){}
.getClass()
.getEnclosingClass()
.getResourceAsStream(name);
try {
properties.load(is);
} catch(IOException e) {
throw new RuntimeException(e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException dontCare) {}
}
}
return properties;
}
}
public class PropertiesExample {
@Inject
public void databaseURL(@Named("db.url") String url) {
System.out.println(url);
}
public static void main(String[] args) {
Injector i = Guice.createInjector(new PropertiesModule());
i.getInstance(PropertiesExample.class); //Output: jdbc:mysql://localhost/test.
}
}
Static Injection
public class StaticModule extends AbstractModule {
protected void configure() {
bindConstant().annotatedWith(Names.named("s")).to("D'OH!");
requestStaticInjection(StaticInjection.class);
}
}
public class StaticInjection {
@Inject
public static void staticMethod(@Named("s") String str) {
System.out.println(str); //Output: D'OH!
}
public static void main(String[] args) {
Guice.createInjector(new StaticModule());
}
}
Custom Scope
public class CustomScopes {
public static final Scope DEFAULT = new Scope() {
public <t> Provider<t> scope(Key<t> key, Provider<t> creator) {
System.out.println("Scoping "+key);
return creator;
}
public String toString() {
return CustomScopes.class.getSimpleName()+".DEFAULT";
}
};
}
public class Person {
public Person() {
System.out.printf("Hi, I'm a Person. With hashCode '%s', I'm unique!%n", super.hashCode());
}
}
public class CustomScopeModule extends AbstractModule {
protected void configure() {
bind(Person.class).in(CustomScopes.DEFAULT);
}
}
public class UseCustomScope {
public static void main(String[] args) {
Injector i = Guice.createInjector(new CustomScopeModule());
i.getInstance(Person.class); //Output: Hi, I'm a Person. With hashCode '7841785', I'm unique!
i.getInstance(Person.class); //Output: Hi, I'm a Person. With hashCode '1847634', I'm unique!
}
}
Using custom scope as an annotation,
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ScopeAnnotation
public @interface DefaultScoped {}
@DefaultScoped
public class Person {
public Person() {
System.out.printf("Hi, I'm a Person. With hashCode '%s', I'm unique!%n", super.hashCode());
}
}
public class CustomScopeModule extends AbstractModule {
protected void configure() {
bindScope(DefaultScoped.class, CustomScopes.DEFAULT); //CustomScopes.DEFAULT is defined in the previous example.
}
}
public class UseCustomScope {
public static void main(String[] args) {
Injector i = Guice.createInjector(new CustomScopeModule());
i.getInstance(Person.class); //Output: Hi, I'm a Person. With hashCode '7841785', I'm unique!
i.getInstance(Person.class); //Output: Hi, I'm a Person. With hashCode '1847634', I'm unique!
}
}
Organizing Modules
If a project has more than a module (i.e service configurations), then...
public class ApplicationModule extends AbstractModule {
protected void configure() {
install(new DefaultScopeModule()); //Install all the modules in the order of depedency?
install(new BindingsModule());
}
}
public class UseCustomScopeWithApplicationModule {
public static void main(String[] args) {
Injector i = Guice.createInjector(new ApplicationModule());
i.getInstance(Person.class);
i.getInstance(Person.class);
}
}
AOP (using) Guice
//The class that would be intercepted.
public class Phone {
private static final Map<number, receiver=""> RECEIVERS =
new HashMap<number, receiver="">();
static {
RECEIVERS.put(123456789, new Receiver("Aunt Jane"));
}
public Receiver call(Number number) {
return RECEIVERS.get(number);
}
}
public class Receiver {
private final String name;
public Receiver(String name) {
this.name = name;
}
public String toString() {
return String.format("%s[name=%s]", getClass().getName(), name);
}
}
import static com.google.inject.matcher.Matchers.*;
public class PhoneModule extends AbstractModule {
protected void configure() {
bindInterceptor(
subclassesOf(Phone.class), //Class Constraint //any(), not(...), annotatedWith(...), subclassesOf(...), only(...), identicalTo(...), inPackage(...)
returns(only(Receiver.class)), //Method Constraint //any(), not(...), annotatedWith(...), only(...), identicalTo(...), returns(...)
new PhoneLoggerInterceptor(),
new PhoneRedirectInterceptor()
);
}
}
public class PhoneLoggerInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
for (Object arg : invocation.getArguments())
if (arg instanceof Number)
System.out.println("CALL: "+arg);
return invocation.proceed();
}
}
public class PhoneRedirectInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
return new Receiver("Alberto's Pizza Place");
}
}
//Bootstrap...
public class MakePhoneCall {
public static void main(String[] args) {
Injector i = Guice.createInjector(new PhoneModule());
Phone phone = i.getInstance(Phone.class);
Receiver auntJane = phone.call(123456789); //Output: CALL: 123456789 Receiver[name=Alberto's Pizza Place]
}
}
Credits: APRESS – Google Guice: Agile Lightweight Dependency Injection Framework, Guice User’s Guice