I’m using Google Guice as my Dependency Injection framework. I wanted to use the javax.cache api aka JSR107 (with EHcache as implementation). If you want to learn more about this famous JSR107, it could be a good idea to go to Greg Luck blog to get some more infos about it.
In my case, I wanted to use the annotations part of the JSR107 because I find them to be more elegant than a pure code solution (understand, you can put cache in your app without changing the logic, you just annotate your business methods, and woosh, you have cache set up).
But the JSR107 annotations implementations are DI framework dependant. And untill some days ago, only Spring and CDI implementation were provided.
I twitted to search any volunteers that might be interested in contributing (with or without me) to a Guice implementation.
Some hours laters, some commits from Michael Stachel were pushed by Alex Snaps to the github repo and here they are, the JSR 107 annotations for Guice were available.
I’d like to show you how you can use them.
The full source code is available here as a Mercurial repository on Bitbucket.
I’m using Maven, so here is the dependency part of the pom
com.google.inject
guice
${guice.version}
net.sf.ehcache
ehcache-jcache
1.4.0-beta1
javax.cache.implementation
cache-annotations-ri-guice
0.5-SNAPSHOT
You can see here that I’m using the latest snapshot of the cache-annotations-ri-guice artifact (0.5-SNAPSHOT as I’m writing this post). I don’t know if the snapshots are available somewhere, so I just cloned the corresponding git repositories and installed the snapshots locally. To be more precise, you will need to clone the jsr107-spec repo to build and install locally the jsr107 api artifacts that are needed by the jsr107 Reference Implementation artifacts, among which you’ll find the cache-annotations-ri-guice artifact.
Now, let’s write a basic UserService interface (ok, you don’t need an interface actually):
package net.awl.ismp.guice.jsr107;
import java.util.List;
/**
*
* @author looztra
*/
public interface UserService {
public User getUserById(int id, int callId);
public List<User> getUserByLastName(String name, int callId);
public boolean calledBy(int callId);
}The UserService makes reference to a User class:
package net.awl.ismp.guice.jsr107;
import java.io.Serializable;
import lombok.Data;
/**
*
* @author looztra
*/
@Data
public class User implements Serializable {
private int id;
private String firstName;
private String lastName;
private int age;
}People that follow may have spotted that I was too lazy to write the accessors and that I chose to use the Lombok @Data annotation instead.
Here is the main part of a simple implementation of the basic UserService:
package net.awl.ismp.guice.jsr107;
import java.util.*;
import javax.cache.annotation.CacheKeyParam;
import javax.cache.annotation.CacheResult;
import lombok.extern.slf4j.Slf4j;
/**
*
* @author looztra
*/
@Slf4j
public class SimpleUserService implements UserService {
private Map<Integer, User> users = new HashMap<Integer, User>();
private List<Integer> callers = new ArrayList<Integer>();
public SimpleUserService() {
initContent();
}
@Override
@CacheResult(cacheName = "getUserById")
public User getUserById(@CacheKeyParam int id, int callId) {
log.info("getUserById(): business method called for id (" + id + ") and callId (" + callId + ")");
registerCaller(callId);
if (id >= 0 && id < 100) {
return users.get(id);
} else {
return null;
}
}
@Override
@CacheResult(cacheName = "getUserByLastName")
public List<User> getUserByLastName(@CacheKeyParam String name, int callId) {
log.info("getUserByLastName(): business method called for name <" + name + "> and callId <" + callId + ">");
registerCaller(callId);
List<User> matches = new ArrayList<User>();
for (Map.Entry<Integer, User> entry : users.entrySet()) {
User potentialMatch = entry.getValue();
if (potentialMatch.getLastName().contains(name)) {
matches.add(potentialMatch);
}
}
return matches;
}
...
}The initContent() an the registerCaller() method are only utilities methods (full source code available here).
The interesting parts are the two annotations @CacheResult and @CacheKeyParam.
Straight from the javadoc: When a method annotated with @CacheResult is invoked a CacheKey will be generated and javax.cache.Cache#get(Object) is called before the invoked method actually executes. If a value is found in the cache it is returned and the annotated method is never actually executed. If no value is found the annotated method is invoked and the returned value is stored in the cache with the generated key.
The @CacheResult is then a very simple way to set up a Read-Through cache.
The @CacheKeyParam is used to specify which subset of the method params are to be used as part of the CacheKey (by default, all the method params are used).
Those 2 annotations are not the only one existing of course. You could use @CachePut to implement a Write-Through cache for instance.
Before it can work as expected, don’t forget to register the CacheAnnotationsModule so that Guice injector can use the jsr107 annotations.
...
@BeforeClass
public void setUp() {
Injector injector = Guice.createInjector(new MyModule(),new CacheAnnotationsModule());
service = injector.getInstance(UserService.class);
}
...MyModule being a very simple Guice module matching my simple needs:
package net.awl.ismp.guice.jsr107;
import com.google.inject.AbstractModule;
/**
*
* @author looztra
*/
public class MyModule extends AbstractModule {
@Override
protected void configure() {
bind(UserService.class).to(SimpleUserService.class);
}
}A simple unit test to see it in action
@Test
public void testGetIdTwice() {
Assert.assertNotNull(service);
int callId = getCallerId();
log.info("testGetIdTwice(): calling the service the first time with callerId <" + callId + ">");
User found = service.getUserById(testGetIdTwice_SEARCHED_ID, callId);
Assert.assertNotNull(found);
log.info("testGetIdTwice(): found user <" + found + ">");
// check that we went through the business method the time
Assert.assertTrue(service.calledBy(callId));
//
int callId2 = getCallerId();
log.info("testGetIdTwice(): calling the service the second time with callerId <" + callId2 + ">");
User found2 = service.getUserById(testGetIdTwice_SEARCHED_ID, callId2);
Assert.assertNotNull(found2);
// check that we did not go through the business method this time
Assert.assertFalse(service.calledBy(callId2));
}
Feel free to get the full source code that is available here as a Mercurial repository on Bitbucket.
Recent Comments