Why use Spring… if you have a HashMap at hand?
Posted by eutrilla on April 7, 2008
Ok, let’s start with a disclaimer. There must be a thousand reasons to use Spring. It’s a terrific piece of work. And nobody can possibly complain about its price.
But it also has its drawbacks. Probably the first one that anybody could think of would be XML config files. Another one is, oddly enough, one of its strong points: it integrates an awful lot of functionality. Although this may be very good indeed if you’re using it all, for simple/medium apps Spring is simply too bloated.
Guice is a step in the right direction, I think. Just a DI container, no more. Google has also chosen to use annotations instead of XML config files, which helps to keep it manageable. But I still have the annoying thought that it shouldn’t be that hard to keep our software nice and clean, without the aid of a whole framework.
Why DI is not always good enough
Maybe the problem with both Spring and Guice is a flaw in the Dependency Injection pattern itself. It looks nice in the simple UML diagrams used to describe the concept, but it gets harder when the number of classes and components increases.
Let’s say that we’re developing a component, and we expect to use it in a DI container. We don’t want to expose too many implementation details, so we decide that all calls from the outside should be made using a façade. Now one of our classes needs to access a log service, and we would like it to be configurable. So we need a setter. Well, actually, we need at least two, one in the calling class and one in the façade, since the former shouldn’t be directly available as part of the public API. They could even be more if the façade doesn’t have directly a reference to the class in need of logging. The more complex the components are, the more likely is this to happen. At the end, we need some extra code to connect services and the classes where they are used.
But now, we have our shiny loosely-coupled component ready for use. So we insert it in our app, together with some tens of other modules. It turns out that all modules require access to the log service. So we have to repeat a very similar setLogger() call tens of times. No wonder DI containers require a LOT of configuration.
Isn’t there a simpler solution?
On the other hand, let’s suppose we forget for a while about Dependency Injection and try another pattern that targets exactly the same use case as DI: the Service Locator. If any of you don’t know what it looks like, I’ll let Martin Fowler introduce it. Maybe it’s not as trendy as DI at the moment, but it’s not anything really new. After all, Service Locator is the very same pattern behind J2EE.
Well, it is quite clear why DI is better, isn’t it? The Service Locator introduces a new dependency on the calling component, so it is not so loosely-coupled anymore. That’s a big problem, right? Well, maybe not.
As Martin Fowler points out in the article previously linked, a dynamic Service Locator can be implemented as just a simple HashMap with some accessors. And has quite a few interesting advantages:
- It is generic, so it is not much a burden dependency-wise.
- If a class inside your components requires access to a new service, you don’t need to change the other classes in the component to propagate a reference from the outside, as long as the service is registered in a static instance of the Service Locator.
- Even if you can’t use a static instance, or you have different contexts which require different sets of services, you only need to propagate one object to access all them: the locator itself.
- Now the configuration is reduced to create and register in the locator the different components during startup.
- It is possible to hot-swap easily implementations of the services.
- No extra framework is needed. Just a HashMap!
Ok, nothing is that nice or that easy
Of course I’m not trying to compare a plain HashMap with the whole Spring Framework, just with the DI-container part of it. And even so, it sure has some drawbacks:
- Configuration (aka Creation and registration of service instances) is done in code. Some may argue that XML-based deployment is more flexible. Personally, I don’t find much harder to edit and compile an initialization manager class, and for complex projects code beats XML.
- The dependency on the locator itself is spread through a good part of the classes that make up the application. Since it is so general, it’s not a big deal. If the Locator were part of the standard Java library, it would be almost like having a dependency to java.lang.String.
- All components should use the same Service Locator class. Otherwise services would have to be registered several times, and design becomes messy. But since Locators can’t be that different from each other, it should be easy to transform one class into another using an adapter. Again, if such simple class was part of the standard Java library, it wouldn’t be a problem at all.
- All service references require a cast. Annoying, but not that bad.
- If components are not well documented, it’s hard to say which dependencies they have, whereas DI-adapted components have the list of dependencies right there in their public API.
But would it work well enough in *real* projects?
I can say that I’ve used this approach in several projects and found it really easy to implement and use. We even made some improvements over Martin Fowler’s initial implementation, though. The keys to store and retrieve the Service instances are no longer Strings, but the class of the desired interface itself. In that way, we avoid typing errors in the identifier and we can place an assert in the service registration method so we can be sure that the instance registered is indeed an implementation of the interface.
Quoting Martin Fowler’s article:
The choice between Service Locator and Dependency Injection is less important than the principle of separating service configuration from the use of services within an application.
To finish, I’d like to mention Netbean’s Lookup class and Java 6’s ServiceLoader class. A very interesting article by John O’Conner (Creating Extensible Applications with the Java Platform) explains the differences between both and how to use them. Again, this is Service Locator in action, but now these classes try to find the services in the classpath instead of needing to be configured during startup. So they are very convenient for plugin-style development, but provide less control over which/how many implementations of the services are used, and therefore target a slightly different use case. But seems to confirm that there are other, simpler options out there to get rid of spaguetti code.