Spring vs. Guice: The Clash of the IOC Containers
By Ryan Nelson
Spring and Google Guice are two powerful dependency injection frameworks in use today. Both frameworks fully embrace the concepts of dependency injection, but each has its own way of implementing them. Although Spring provides many benefits, it was created in a pre-Java-5 world. The Guice framework takes DI to the next level, leveraging the full power of Java typing, especially annotations and generics. Discover how Guice can make your code more modular, easier to write, and less error prone to maintain.
Guice or Spring?
Spring and Google Guice are two powerful dependency injection frameworks in use today. Although both frameworks fully embrace the concepts of dependency injection, each has its own way of implementing them. Spring was one of the first DI frameworks available, and it offered a lighter-weight alternative to the heavy-handed approach of J2EE. In the years since, Spring has built a loyal following and boasts an impressive application stack.
But Spring’s approach to the dependency injection model has remained largely unchanged. The advent of Java 5 brought significant changes to the language like generics and annotations: features that enhance the power of Java static typing. Guice is a DI framework that was built from the ground up with the intent to take full advantage of these new features and that has focused on one primary goal: to do dependency injection well.
Let's take a look at some of the core differences between the two, and see why I prefer to use Guice.
1. Living in XML Hell
In Spring, classes are traditionally wired together using XML metadata. Unfortunately, wiring classes in a format other than Java means losing all the benefits of the Java compiler and type checking. Configuring an application with XML often means manually digging through those files to read how classes are wired, rather than leveraging an IDE's ability to link them based on type. Refactoring also becomes a pain: moving packages or renaming a class requires ensuring those fully-qualified names are also changed in the XML files. Spring treats wiring as though it’s configuration data, rather than as another form of code.
Counter-point A: "But that’s why we have Spring IDE!" Spring IDE is undoubtedly a critical tool for Spring application development. It provides a lot of the safety checks and other benefits that are abandoned when writing code in declarative XML rather than compiled Java, but this just reinforces my point. Why should you need to use a special 3rd-party tool just to be able to type-check, navigate, and refactor code, when you could get it all for free with the Java compiler and the IDE of your choice? Spring IDE is a nice answer to a question that doesn't need to be asked when your code is wired in Java.
Moreover, as a developer I often need type checking in places an IDE can’t go, such as during continuous integration. Spring offers no help in ensuring type safety during an automated build without again requiring a tool specially written for that purpose.
Counter-point B: "But that's why we have JavaConfig!" Indeed, the Spring authors seemed to sense the limitations of XML, and it led them to create an eponymously named Java configurable version. Spring JavaConfig is a step in the right direction, and it mitigates many of my complaints about forgoing the compiler. Nonetheless, it has a very bolted-on feel, and its need to maintain a 1:1 relationship with XML config has left it suffering by proxy from some of XML’s shortcomings.
In addition, I don't see JavaConfig as widely adopted as XML in Spring development, despite the fact that it's been available for a few years. Spring has made an effort to increase adoption by including many of JavaConfig's features in Spring 3, but progress is slow and comments on Spring JavaConfig's dedicated forum look nearly dead. It seems to me the Spring community has not embraced the benefits of JavaConfig, which is a shame.
Those facts considered, Google Guice offers a better solution: allowing Java types to dictate configuration. Guice wiring is type-checked at compile-time, and basic IDE functionality makes refactoring and code navigation a cinch. This elevates types to the role of first-class citizens in application wiring, and keeps Guice configuration modules lightweight.
2. Eliminating reliance on String identifiers
An outgrowth of the criticism of Spring XML is its heavy dependence on String identifiers. Because with XML you no longer have type safety, Spring only requires that you specify class names in bean definitions, with a special String identifier that is referenced throughout the rest of the XML configuration (and even in Java code). Configuration is further complicated by Spring’s support for aliases, which permit you to assign multiple names to a single bean, if perchance you are wiring two systems together that reference the same bean by different names.
I assume I don't have to spend a long time explaining why String identifiers are inferior to actual types. It's one of the same reasons type-safe enumerations were added to the Java language: typos are a risk and don't get caught until runtime. Refactoring is likewise painful.
Again, Spring IDE can help us out here, and again I ask: why would we need it, if we just used the Java compiler? Someone spent a lot of time writing a nice IDE to do something the compiler will do for you already, assuming you wire classes together by type. Spring JavaConfig mitigates this somewhat, since it allows you to reference beans by type, but even JavaConfig has its limitations. Like in XML, as soon as you want to define the same class twice--perhaps with two different configurations--even JavaConfig forces you to use String identifiers to distinguish them.
Guice, on the other hand, uses type information to wire its classes together. When two different configurations of the same class are needed, Guice uses binding annotations--annotations that serve as marker interfaces--to distinguish them. Guice does support a type of String identifier via the @Named annotation, but its use is discouraged for the same reason. For avoiding wiring mistakes, the compiler is your best friend, one fully taken advantage of by Guice’s emphasis on wiring by type.
3. Preferring Constructor Injection
Although Spring and Guice both support constructor and setter injection, each framework has a preference. Spring has long favored setter injection. Back in the early days of Spring, the authors believed the lack of argument names and default arguments in constructor injection reduced clarity. In addition, constructor injection makes it difficult to have optional dependencies, requires dependencies to be configured in a specific order, and forces subclasses to deal with superclass dependencies. Using setter injection eliminates these problems, and so Spring favors that approach.
The Guice authors saw difficulties with setter injection. One problem is immutability: it is impossible to make immutable a class that uses setter injection. Constructor injection, on the other hand, makes the creation of immutable classes easy, an important consideration in writing multi-threaded applications. In addition, optional dependencies, while perhaps convenient, can introduce confusion about how a class is configured at runtime. Configuring a class through setter injection can often lead to missed required dependencies. Though Spring does provide a @Required annotation to solve this problem, using constructor injection eliminates it by default.
Constructor injection also makes a class's dependencies immediately clear at a glance. If you're writing or modifying a unit test, it's easy to read what the system-under-test needs. Lastly, because Guice uses types to wire classes together, constructor argument order isn't an issue. You can feel free to reorder them how you want without needing to modify configuration code at all.
The potential drawbacks posed by setter injection outweigh the benefits in many common scenarios, and so Guice established a best practice of favoring constructor injection instead. Its API is well-suited to that approach. But if you should choose to switch from one form of injection to the other, Guice makes that easy too. Changing from setter to constructor injection or vice versa is simply a matter of modifying the class in question. Unlike in Spring, you need never touch a configuration file.
4. Nullifying NullPointerExceptions
Null is easily one of the most non-communicative return values possible from a method call. Its existence means we have to pepper our code--including all our public methods--with is-null checks. And who can deny the ubiquity of one of the most vexing of Java exceptions: the dreaded NullPointerException? I have spent lots of effort evangelizing colleagues to minimize the use of nulls as much as possible.
Guice and Spring both permit nullable and non-nullable injection, and again, each has a preference. Spring permits you to explicitly inject null (it even has a <null/> XML tag) and attempts to play nicely with accidental nulls. If a bean reference returns a null, Spring has no problem injecting that into other objects.
Conversely, Guice hates nulls as much as I do. By default, Guice refuses to inject a null into any object, and if an accidental null shows up during wiring, it fails-fast with a ProvisionException. Guice does allow for the exception case by permitting fields to be annotated with @Nullable, but this is discouraged.
5. Intruding into the domain
Spring advocates, especially XML acolytes, tout their framework’s separation from the domain as a major selling point. In fact, the chief complaint I hear about Guice's approach is that it couples application code too tightly to the DI framework. This is a minor concern, in my experience.
First, Guice is only "intrusive" as it concerns metadata, and even then, the vast majority of code in a Guice-based application will see only one Guice-specific annotation, @Inject. If later you decide you want to eliminate Guice from your application, you could do so simply by eliminating the run-time dependency. You would then be free to use Spring, or another DI framework, or even write your own factories. True, it would require leaving a Guice jar in the compile classpath, but you would not need to recurse through the entire code base, ripping out Guice annotations as some have suggested. If the thought of having @Inject sprinkled through your codebase is still unbearable, Guice Provider methods are an alternative very similar to Spring JavaConfig to inject instances without using any Guice-specific annotations.
Nonetheless, Spring has also embraced framework-specific annotations, even when using XML config (for example, the @Required annotation discussed previously). Moreover, Spring--along with Guice--was an integral member of JSR-330, which defines a core set of common DI annotations, including javax.inject.@Inject. Thus, any JSR-330-implementing framework will be interchangeable, from an application code perspective.
Second, switching out DI frameworks is a relatively infrequent activity anyway. Though keeping application code perfectly separate from the framework does make this easier, it may force you to miss out on key features for a questionable tradeoff in benefits. The Guice authors understood this, and as a result, Guice doesn't advertise itself as a mutually exclusive replacement for Spring. From version 1.0, Guice provided a SpringIntegration module which allows Guice to access Spring beans, thus letting the two frameworks co-exist in harmony and facilitating a smoother migration strategy.
Indeed, this complaint seems a little disingenuous coming from the Spring camp in the first place. Yes, a well-configured Spring application has code completely free of references to the framework. But in order to properly maintain a large-scale application in Spring, you really must take advantage of the features of Spring IDE. Does having @Inject in your code make you all that more coupled to a framework than dependence on a specific 3rd-party tool for development?
6. Replacing Spring verbosity with Guicey compactness
The scenario where Guice shines the most and makes its strongest case in comparison to Spring is in configuration complexity when reusing dependencies.
Unless you use auto-wiring, which is universally discouraged on any project of scale, Spring requires that all dependencies be explicitly defined. That means, if you have 50 places where you want to add a dependency, you must modify 100 declarations of that dependency: 50 source files and 50 bean definitions in one or more configuration files. If you later change your mind and want to use a completely different dependency, you must again modify both source and configuration files. This process is error-prone and leads to duplicated effort.
Leveraging Java types, Guice eliminates this extra complexity. If you want to add or delete a dependency from 50 different classes, all you have to do is modify 50 source files, and then modify a single configuration binding. In some cases, such as the injection of concrete classes, you may not need to modify configuration at all. The use of Java typing again leads to more sensible code, relying on the Java compiler to do the work for you.
7. Considering other advantages
There are many other benefits Guice offers that give it advantages over Spring. Guice fully supports generics, and has introduced mechanisms such as TypeLiterals and Provider methods to circumvent type erasure. Though Spring offers some basic support for generics by offering type-conversion for collections, its traditional reliance on XML limits its flexibility.
Another excellent Guice advantage is its first-class error messaging. Wiring classes by type and disallowing the injection of nulls gives Guice a distinct advantage at run-time: many errors can be caught at provisioning time, as soon as an injector is created. Guice aggregates these errors and displays them all at once in a clean format, helping you to resolve all configuration problems at once. Spring, on the other hand, fails after the first wiring error it encounters and provides sometimes cryptic error messages with lengthy stack traces. Worse, in the case of the aforementioned parameterized types, you may not even get an error message until deep into your application. Author and Guice advocate Robbie Vanbrabant demonstrates some of these differences with concrete examples.
Guice is also very fast, an even more impressive feat considering that by default Guice injects a new and separate instance of an object for each dependency (called “prototype” scope by Spring), whereas Spring provides singletons by default.
In conclusion, although this article may appear to be anti-Spring, I know the framework has value. Spring challenged the J2EE beast, brought dependency injection into the mainstream, and today offers an impressive stack of useful applications built on top of its IoC container. It was created in a pre-Java-5 world--a world in which a framework like Guice could not have even existed.
The Guice framework takes dependency injection to the next level, leveraging the full power of Java typing, especially annotations and generics, to make powerful DI applications more modular, easier to write, and less error prone to maintain.
Ryan Nelson has more than 10 years of software development experience on both large and small projects. He presently develops educational software as a Senior Software Developer for Pearson Digital Learning in Chandler, Arizona. He can be reached at firstname.lastname@example.org
Dependency Injection ~ Dhanji R. Prasanna
09 Aug 2010