Wednesday, April 16, 2008

Using Annotations in Spring 2.5 to the Extreme

For the past couple of years I have been using Spring in almost all of my projects. It has been mostly a great experience. However, there was always a lot of XML involved in order to wire all those beans together. This held true also for Hibernate as well as other frameworks. Did I mention Acegi Security?...

Well, then the community decided that annotations were an awesome way of cutting down on all this configuration clutter. Paired with the concept of sensible defaults brought over from the Rails community, live started to get easier. Guice introduced annotations-based dependency injection (DI), which has also been adopted by Spring. Thus, if you use JPA or Hibernate Annotations combined with all the whiz-bang offered by Spring you can virtually eliminate all your XML glue-code.

Well, with Spring 2.5 you can take DI to the extreme by also eliminating all your dependency-setters. For example, in your service methods you can annotate your private data access object variable declarations with annotations (e.g. @Autowired) that will inject the necessary dependencies for you. No need to declare public setters.

While this sounded kind of exciting and works well, I started scratching my head when dealing with unit-testing. How do I mock my service class' dao objects when there are neither public setters nor respective constructors?

For this issue Spring comes to the rescue with the following helper class, which provides helper methods for setting non-public variables or setters:

org.springframework.test.util.ReflectionTestUtils

Here is an example using EasyMock for creating a partial mock object:

...
final User Dao userDao = EasyMock.createStrictMock(UserDao.class);
EasyMock.expect(userDao.save(user)).andReturn(user);
ReflectionTestUtils.setField(userService, "userDao", userDao, UserDao.class);
EasyMock.replay(userDao);
...

And in my Service class I declare:

...
/** User Dao. */
private @Autowired UserDao userDao;
...

For more information regarding the new features in Spring 2.5 check out this article at InfoQ as well as this blog entry.

This very streamlined way of doing dependency injection without setters is very interesting, but to me it poses the following question:

Is this usage of annotations pushing the envelope a little too far?

It certainly binds you code more to the underlying framework (Spring) but on the other hand it feels kind of nice and works well. In my understanding this might be a really productive way of implementing smaller projects but it might be problematic for bigger ones...

2 comments:

Ignacio Coloma said...

Did you try to override your UserDao declaration with a second spring.xml file intended for tests?

Something like:

protected String[] getConfigLocations() {
return new String[]{ "spring-config.xml", "mock-daos.xml" };
}

And override in mock-daos you DAO declaration to use EasyMock factory methods?

Gunnar Hillert said...

¡Hola Ignacio!
Muchas gracias por su consejo. Well, this is certainly a possibility. However, while I use the Spring context in integration tests, I rather not use it for pure unit tests as this would slow down my tests' execution). Probably a matter of taste...