ON PROMOTING DEPENDENCY INJECTION WITH GUICE
By Thang M. Le
Other Google Guice tutorials on TheServerSide.com:
By submitting your email address, you agree to receive emails regarding relevant topic offers from TechTarget and its partners. You can withdraw your consent at any time. Contact TechTarget at 275 Grove Street, Newton, MA.
Guice is known as an annotation based dependency injection framework. Being a relatively new player in dependency injection topic, Guice has quickly become popular thanks for its effort to make dependency injection as simple as annotating codes with @Inject. Guice pioneers the use of annotation in dependency injection as it believes Java annotation can assist to accomplish dependency injection easily, quickly and safely. Not only its easy-to-use advantage, Guice framework has also shown its strengths in flexibility, scalability and last but not least maintainability. There have been numerous tutorials published on the Internet showing step by step on how to use Guice. All have paved a way to make Guice a favorable framework when it comes to dependency injection. Despite all of the great things for which Guice is well-known, there are some important concerns which have not been fully discussed. These either directly come from the use of Java annotation or indirectly inherit from dependency injection patterns. This paper is going to discuss these issues with a purpose to draw attention to developers who are going to adopt Guice into their day-to-day task. All discussions described in this paper are relevant to Java language and Guice 2.0. The paper also assumes readers are little familiar with dependency injection topic and Guice.
Dependency Injection & Guice Dependency Injection Framework
Dependency injection (DI) refers to a way which allows injecting a desired implementation into a dependency. DI can only be done provided codes already follow DI patterns. Some of popular DI patterns are constructor injection (CI) pattern, setter injection (SI) pattern and method injection (MI) pattern. CI pattern and SI pattern are somewhat similar. They both move dependencies to class fields. The difference between the two is CI pattern accepts the implementation through constructor argument while SI pattern accepts the implementation through setter argument. Compared with CI pattern and SI pattern, MI pattern places dependencies on method arguments. One can easily recognize MI pattern owes its idea to dependency inversion principle which is discussed in The Dependency Inversion Principle by Robert Martin.
In object-oriented language, codes are broken into different classes. Each class in turn has relationships with other classes. In general, there are 3 types of class relationship: “is-a”, “has-a” and “use-a”. Dependency is defined in “use-a” relationship where the end-point class is a dependency of the start-point class. Relationship among classes of an application can be pictured as a connected graph where class is a node and class relationship represents an edge connecting two nodes. Given a graph with N nodes, there are as many as NxN edges. An application embracing a good design forms a sparse graph where the ratio of number of nodes to number of edges is kept high while traversing among nodes is maintained simple and oriented. Design patterns are among many practices which contribute to a better design. In contrast, DI patterns neither better nor worsen an existing design. DI patterns soften “use-a” relationship by moving the dependency to places where it can directly receive different implementations from outside. This process requires code changes but the existing design remains.
DI works on the concept of pre-allocating objects well in advance to their use. This concept is another form of the well-known technique data pre-fetching mostly used in operating system and database for optimization. In the case of using CI pattern and SI pattern, dependency instance is constructed and assigned to class field. It would be bogged down to do all redundant tasks such as constructing an instance and injecting it to dependency. DI framework stands to assist these tasks. It is a framework designed to work with codes already follow DI patterns. DI framework replaces boilerplate codes with its standardized way for performing dependency injection. Developers only need to provide binding information. With Guice, this task includes annotating codes with @Inject and writing logic for corresponding binding module. Guice framework provides a simple solution, by mean optimal, to the common DI problem without going into a complex design. Codes are simplified is an advantage when doing dependency injection with Guice.
As mentioned above, DI framework works upon codes comply with DI patterns. This requirement implies codes might have to go through certain changes to be DI capable. Such changes are not safe. Applying DI patterns to existing codes might break some working functionalities. CI pattern and SI pattern are the most concerns.
Singleton scope can only apply to “stateless instance”. A stateless instance shares many commons with stateless session bean in EJB:
A stateless session bean does not maintain a conversational state for a particular client. When a client invokes the method of a stateless bean, the bean's instance variables may contain a state, but only for the duration of the invocation. When the method is finished, the state is no longer retained. Except during method invocation, all instances of a stateless bean are equivalent, allowing the EJB container to assign an instance to any client.
The most important attribute which every stateless instance must hold is it must not maintain a conversational state for a particular client.
LoginModule maintains conversational data (user and password) for a particular client in order to executing signIn() method. Hence, this class cannot be used either directly or indirectly in singleton scope regardless its implementation is thread-safe or not.
A class with no class field is immediately stateless, hence, it can operate in singleton scope. Applying CI pattern and SI pattern results in new class fields are added into the class. The existence of the new class fields creates additional burdens to determine whether an instance of the new class is still remained stateless. This task includes one must ensure that a singleton instance must not hold conversational data and apply this requirement to all of its instance variables recursively. The trouble is these new class fields represent dependencies which have their implementations injected at runtime. In theory, one does not know which implementation of a dependency is going to be used. As a result, the class should no longer be used in singleton scope.
The Serialization Issue
By definition, a class is serializable if and only if all of its fields are serializable. Dependencies might not be serializable for some reasons such as holding a database connection. Hence, putting dependency to class field might break serialization of a class. This problem can be overcome by using transient keyword for all class fields representing dependencies.
Default Constructor Issue
A class without any declared constructors implicitly inherits default constructor. Applying CI pattern to a class results in new constructor is introduced. The newly created constructor disqualifies this class from inheriting default constructor. Unless explicitly declaring one, the class no longer has default constructor. Without careful consideration, missing default constructor in a class potentially breaks some existing features relying on the appearance of the default constructor.
Large Memory Footprint Issue
Objects allocated during method execution are subjects to garbage collection once the execution is complete. CI pattern and SI pattern effectively bring dependency to class field, which inadvertently slows down the process of reclaiming memory taken by dependency instance. Therefore, an application might experience a larger memory footprint especially since singleton scope cannot be used.
Limitation of Guice Linked Binding
With Guice, after annotating codes with @Inject, the next step is to write binding module. Binding module is the place where Guice will look for guidance to resolve dependencies. Guice supplies different binding methods which can be used in binding module. Among supported binding methods, linked binding is the most popular one.
Linked binding is used to “map a type to its implementation”. In above example, XMLParser is a type and DOMParser is an associated implementation. However, how to construct an instance of DOMParser is unknown. If DOMParser class has many constructor methods, it is important to let developers determine which constructor method is going to be used when instantiating the class. Linked binding does not provide this ability (perhaps by its design), which makes it less useful.
Limitation of Guice Instance Binding
As a resolution to the limitation of linked binding, instance binding can be used to bind an instance to a type. Instance binding promotes “map a type to an instance”.
DOMParser parser = new DOMParser();
Instance binding can be used alone or can be chained with linked binding. Alternatively, one can also use @Provides method or Provider binding when dealing with complex object instantiation. With instance binding, developers manage to construct an instance by themselves then pass it to Guice so that the instance can be injected to its corresponding type. Instance binding works well in most cases. But it falls short when many dependencies of the same type exist.
In above example, XMLProcessor has domParser and saxParser as its fields and they both have the same class type XMLParser. In this case, dependencies (domParser and saxParser) cannot be correctly injected through any supported injections in Guice. This limitation elaborates on how Java reflection works. Accordingly, fieldname cannot be retrieved through Java field reflection and constructor parameter name cannot be retrieved through Java constructor refection. Without these data, Guice cannot perform proper injection. Fortunately, developers can use @Named annotation to workaround this limitation by tagging @Named with different names to differentiate dependencies and precisely refer to these names in a binding module.
However, the use of @Named annotation brings up some concerns. It requires developers to hard-code a name used with @Named and refer to this name later in binding module. This type of relationship is weak and prone to failure. Also, codes become awkward with all hard-coded names used with @Named.
Functional Dependencies vs Testing Dependencies
Guice framework makes codes more testable. This is partially true. Guice limits its use to a set of dependencies representing functional dependencies. When it comes to unit testing, codes are facing with a larger set of dependencies which account for testing dependencies. Testing dependencies are much different from functional dependencies. While it is useful to make functional dependencies injectable, it is impractical to do the same for testing dependencies.
Calendar currentTime is a testing dependency. Method doAction() heavily depends on the data of currentTime to execute corresponding logic. Obviously, one would want to test doAction() with a set of specified data for currentTime instead of unpredictable data returned by Calendar.getInstance(). Should DI technique be used in this case?
Above example clearly indicates DI is not a right tool for testing. Developers should avoid using Guice for testing purpose alone. Adopting Guice requires amount of code changes. In some cases, some of these changes are impractical because they do not produce any improvement to the application. In addition, it is recommended an application should be free from any testing-aware codes. Annotating codes with @Inject to make codes more testable violates this rule. One might find binary manipulation technique to be useful when dealing with testing dependencies. Tools such as javassit, asm are great resources to use in unit testing.
Simplifying DI with DependencyBean
CI pattern and SI pattern leave a class with many class fields. The number of fields linearly increases with the number of existing dependencies. To avoid this problem, one can stuff all required dependencies in DependencyBean class then using setter/getter of DependencyBean to set/retrieve a desired dependency instance. This way produces a class with only one field holding reference to DependencyBean instance. Developers still have to populate DependencyBean instance with a desired implementation for each dependency and ask Guice to inject this instance into the class.
This paper mainly focuses on trade-offs when using DI in a project. Guice is taken as an example to illustrate some limitations of annotation based DI framework. Developers might find the need to use DI framework for different purposes but testing. Making codes more testable is not persuasive enough to adopt DI/Guice into an existing project. This paper should not discourage the use of DI/Guice in a project. It rather provides enough information to help developers make their own decision whether DI is suitable for their project and which DI framework should be used.
Guice Dependency Injection Framework
K.Lieberherr, I. Holland, A.Riel – Object-Oriented Programming: An Objective Sense of Style
Martin Fowler – Inversion of Control Containers and the Dependency Injection pattern
Robert Martin – OO Design Quality Metrics – An Analysis of Dependencies
Robert Martin – The Dependency Inversion Principle
Here are a few of the previous tutorials on Google Guice that have been featured on TheServerSide.com
Tutorial 1: Getting Started with Google Guice: Environment Setup and Configuration
Tutorial 2: The Simplest IoC Example You've Ever Seen, with Google Guice