As I recently played around a bit with the creation of MD5 hashes in Java, I stumbled across an alternative MD5 library called "Fast MD5", which claims to be faster than the classes provided by the JDK. So I downloaded this lib, added it to my class path and started using it by commenting out the references to JDK's class MessageDigest and using Fast MD5's class MD5 instead.
Everything worked out fine, but I was not very satisfied from a software architectural point of view. Instead of toggling referenced classes by commenting and uncommenting code, the proper way to go should be dependency injection (DI), and I thought it would be a great chance to get a grip on Google's award-winning DI framework Guice. There we go ...
Extract the interface
One of DIs basic principles is the separation between the interface of a service and its implementation. So lets extract an interface out of last post's class MD5Helper and make this class become an implementation of that new interface:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@ImplementedBy(MD5HelperStandardImpl.class) public interface MD5Helper { String getMD5Hash(String input); } public class MD5HelperStandardImpl implements MD5Helper { @Override public String getMD5Hash(String input) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] messageDigest = md.digest(input.getBytes()); return new BigInteger(1, messageDigest).toString(16); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } } |
Two things are remarkable:
- The interface is annotated with Guice's @ImplementedBy annotation, specifying the interface's default implementation
- The method getMD5Hash() became non-static, as static methods can't be declared within an interface.
May I introduce: The injector
How to use Guice now to get an instance of MD5Helper? Nothing could be easier. Just use Guice's injector as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class MD5HelperTest { @Test public void testStandardImpl() throws Exception { Injector injector = Guice.createInjector(); MD5Helper helper = injector.getInstance(MD5Helper.class); assertEquals( "5d255e82ae2be1d1d54090bb20351106", helper.getMD5Hash("Hello guicy world!")); } } |
Custom bindings
Let's now create another implementation of the MD5Helper interface based upon the mentioned library "Fast MD5":
1 2 3 4 5 6 7 8 9 10 11 |
public class MD5HelperFastImpl implements MD5Helper { @Override public String getMD5Hash(String input) { MD5 md5 = new MD5(); md5.Update(input); return new BigInteger(1, md5.Final()).toString(16); } } |
How can we make sure to get this implementation, if we request an instance of MD5Helper? Obviously there can be only one default implementation, so there has to be another way to tell Guice which implementation we want.
And indeed there is. All we have to do, is to provide the injector with a so called module, that specifies the binding between the interface and the implementation, as shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class TestModule extends AbstractModule { public void configure() { bind(MD5Helper.class).to(MD5HelperFastImpl.class); } } public class MD5HelperTest { ... @Test public void testFastImpl() throws Exception { Injector injector = Guice.createInjector(new TestModule()); MD5Helper helper = injector.getInstance(MD5Helper.class); assertEquals( "5d255e82ae2be1d1d54090bb20351106", helper.getMD5Hash("Hello guicy world!")); } } |
Finally ... dependency injection!
Now imagine, there was another class which wants to make use of the MD5Helper. This class could make direct use of the injector itself, but actually this would degrade Guice to just some better sort of factory. Rather we can use Guice to inject dependency – actually that's what Guice is all about :-) How to do so? Have a look at the following example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class PasswordManager { @Inject private MD5Helper helper; private Map passwordHashes; public PasswordManager() { passwordHashes = new HashMap(); passwordHashes.put("peter", "5ebe2294ecd0e0f08eab7690d2a6ee69"); } public boolean passwordMatches(String userName, String enteredPassword) { return passwordHashes.containsKey(userName) && helper.getMD5Hash(enteredPassword).equals(passwordHashes.get(userName)); } } |
The example shows a simple password manager. It checks the password some user entered by comparing the MD5 hash of that entered password with the saved hash of the user's password.
Disclaimer: This code is just for demonstration purposes, a real implementation would include retrieving the existing hash from some sort of persistent storage and using some salt for hash generation. Generally it is recommendable to represent passwords with character arrays instead of strings, allowing for removing them from memory after processing, as described in JDK 6' Console class.
Back to the example. We make use of Guice's @Inject annotation to provide the PasswordManager with an MD5Helper. If we use Guice now to get an instance of PasswordManager, this dependency will be injected:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class PasswordManagerTest { private PasswordManager passwordManager; @Before public void setup() { Injector injector = Guice.createInjector(); passwordManager = injector.getInstance(PasswordManager.class); } @Test public void testPasswordMatches() throws Exception { assertTrue(passwordManager.passwordMatches("peter", "secret")); assertFalse(passwordManager.passwordMatches("peter", "wrong password")); } } |
So actually you will very rarely make direct use of the Injector – only once at the top of your object graph. All dependencies annotated with @Inject in the root class (here PasswordManager) as well as in all classes referenced by it will be resolved by Guice automatically.
What's next?
Try it out yourself – download the source code of this tutorial and give Guice a try. The sources come in form of a Maven project. Just extract the archive and run mvn test. Maven will download Guice and run the tests of the tutorial.
That's the end of our 15 minute crash course in using Google's Guice, though that's surely not all to be said about this fantastic framework. Definitely Guice's user guide is worth reading – especially due to its refreshing brevity (approx. 20 pages).
Advanced features include custom binding annotations, scoped dependencies and a means of intercepting methods allowing for the implementation of cross-cutting concerns in an aspect oriented way of programming.
3 comments:
That is a great tutorial. Well explained and brief. Thanks
I get this maven error ?
[ERROR] The project org.gm:hashcache:1.0-SNAPSHOT (C:\guice-tutorial\pom.xml) has 1 error
[ERROR] 'build.plugins.plugin.version' for org.apache.maven.plugins:maven-compiler-plugin must be a valid version but is 'RELEASE'. @ line 32, column 14
Just replace the "version" element for the compiler plugin in the pom.xml with the version you want to use.
Post a Comment