For the past two and a half years I have worked primarily on standalone Java applications using Spring/Relational databases and messaging software such as Websphere MQ.
The Well Grounded Java
Developer by Ben Evans and Martijn Verburg mentions Guice as an alternative
to Spring for dependency injection. I like learning new technologies so I've decided to give it ago!
Here's my attempt at getting a little Guice application up and running. Firstly, I'm going to use Gradle and I'm going to get the Guice jars from the maven central repo. Here is my gradle build file:
apply plugin: 'java'apply plugin: 'idea'
repositories { mavenCentral()}
dependencies { compile 'com.google.inject:guice:3.0'
testCompile 'junit:junit:4.11'
testCompile 'org.mockito:mockito-all:1.9+'
}
I've also applied the idea plugin as I am trying out IntelliJ this week after seven years of Eclipse!
So I want a simple application that stores expenses. I am currently spending far too much money on coffee and want
to start tracking it.
Lets start with a simple interface:
package com.batey.expense.store;
public interface ExpenseStorer {
void storeExpense(Expense e);
Expenses lookupExpenses(String name);
}
With a simple implementation:
package com.batey.expense.store;
import
com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
@Singleton
public class InMemoryExpenseStorer implements ExpenseStorer {
private static final Logger LOGGER = LoggerFactory.getLogger(InMemoryExpenseStorer.class);
private static final Expenses NULL_EXPENSE = new Expenses();
private Map<String, Expenses> expenses = new HashMap<>();
@Override
public void storeExpense(Expense e) {
Expenses expenses1 = expenses.get(e.getName());
if (expenses1 == null) {
expenses1 = new Expenses();
expenses.put(e.getName(), expenses1);
expenses1.setGroupName(e.getName());
}
expenses1.getExpenses().add(e);
LOGGER.info("Got {}", expenses1);
LOGGER.info("Updated expenses {}", expenses);
}
@Override
public Expenses lookupExpenses(String name) {
Expenses expenses1 = expenses.get(name);
LOGGER.info("Current expenses {}", expenses);
if (expenses1 == null) {
expenses1 = new Expenses();
expenses1.setGroupName(name);
expenses.put(name, expenses1);
}
LOGGER.info("Returning {}", expenses1);
return expenses1;
}
}
Now I want to use this class in another part of my system. Lets say we have another class called BudgetTracker:
package com.batey.expense.budget;
import com.batey.expense.store.ExpenseStorer;
import com.batey.expense.store.Expense;
import com.batey.expense.store.Expenses;
import
com.google.inject.Inject;
import java.math.BigDecimal;
public class BudgetTracker {
@Inject
private ExpenseStorer expenseStorer;
public void buySomething(String description, String who, BigDecimal howMuch) {
Expense expense = new Expense();
expense.setAmount(howMuch);
expense.setName(who);
expense.setDescription(description);
expenseStorer.storeExpense(expense);
}
public BigDecimal howMuchHaveISpent(String who) {
Expenses expenses = expenseStorer.lookupExpenses(who);
return expenses.getTotal();
}
}
So how do we run this? For that we need a Guide module:
package com.batey.expense.store;
import com.google.inject.AbstractModule;
public class ExpenseModule extends AbstractModule {
@Override
protected void configure() {
bind(ExpenseStorer.class).to(InMemoryExpenseStorer.class);
}
}
And then I put together a very simple main method:
package com.batey.expense.budget;
import com.batey.expense.store.ExpenseModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import java.math.BigDecimal;
public class Main {
public static final void main(String[] args) {
Injector injector = Guice.createInjector(new ExpenseModule());
BudgetTracker budgetTracker = injector.getInstance(BudgetTracker.class);
budgetTracker.buySomething("A tv", "Chris", new BigDecimal("100.00"));
budgetTracker.buySomething("A cake", "Chris", new BigDecimal("50.00"));
System.out.println("Chris has spent: " + budgetTracker.howMuchHaveISpent("Chris"));
}
}
And then running this:
20:39:46.168 [main] INFO c.b.e.store.InMemoryExpenseStorer - Got Expenses{expenses=[Expense{name='Chris', amount=100.00, description='A tv'}], total=0, groupName='Chris'}
20:39:46.171 [main] INFO c.b.e.store.InMemoryExpenseStorer - Updated expenses {Chris=Expenses{expenses=[Expense{name='Chris', amount=100.00, description='A tv'}], total=0, groupName='Chris'}}
20:39:46.172 [main] INFO c.b.e.store.InMemoryExpenseStorer - Got Expenses{expenses=[Expense{name='Chris', amount=100.00, description='A tv'}, Expense{name='Chris', amount=50.00, description='A cake'}], total=0, groupName='Chris'}
20:39:46.172 [main] INFO c.b.e.store.InMemoryExpenseStorer - Updated expenses {Chris=Expenses{expenses=[Expense{name='Chris', amount=100.00, description='A tv'}, Expense{name='Chris', amount=50.00, description='A cake'}], total=0, groupName='Chris'}}
20:39:46.172 [main] INFO c.b.e.store.InMemoryExpenseStorer - Current expenses {Chris=Expenses{expenses=[Expense{name='Chris', amount=100.00, description='A tv'}, Expense{name='Chris', amount=50.00, description='A cake'}], total=0, groupName='Chris'}}
20:39:46.172 [main] INFO c.b.e.store.InMemoryExpenseStorer - Returning Expenses{expenses=[Expense{name='Chris', amount=100.00, description='A tv'}, Expense{name='Chris', amount=50.00, description='A cake'}], total=0, groupName='Chris'}
Chris has spent: 150.00
Key points:
- We use the @Singleton on the ExpenseStorer to say we only want one of these in our application. Singleton is
the default in spring: not so in Guice.
- We use @Inject to identify a field needs injected. This could have also been done via the constructor. This
is like the spring @Autowired
- Inside the ExpenseModule we bind any injected ExpenseStorers to the InMemoryExpenseStorer implementation.
Success! From never having used Guice getting this together was very straightforward and took under thirty minutes.
My initial thoughts of Guice for dependency injection:
- It is very simple! I don't remember how long my first Spring application took me to get going but I bet it
was longer than this!
- No XML! Some people love configuration XML; others hate it. Personally, I'm sitting on the fence. In the
latest applications I've written I've used a combination of annotations Java config along with XML
config. Guice is pure Java config and I'd have to see a large scale project using it before passing
judgement on that.
- Standardisation! Guice is the first implementation of JSR330 - making DI standardized in Java can only be a
good thing.
I'm going to keep building my expense application with Guice and will post more about it when I have used it
more!