This week I finish the Inversion of Control / Dependency Injection by looking at some of the other IoC mechanisms. I have already mentioned that DI is only one of the available IoC mechanisms.
You can find the previous articles at:
Inversion of Control (IoC) Types
In general, IoC can be grouped into two subtypes: Dependency Lookup and Dependency Injection.
- Dependency lookup is a more traditional approach, and should be familiar to old-time Java programmers. With dependency lookup, a component must look up a reference to a dependency.
- Dependency injection is a newer approach that is more flexible and easier to use than dependency lookup. With dependency injection, the dependencies are injected into the component by the IoC container. The component doesn’t know or care how the dependency was created.
Dependency lookup has two common types:
- Dependency Pull is the most familiar type of IoC. Dependencies are pulled from a central registry as required. To access an EJB (version 2.1 or earlier) we used dependency pull via the JNDI API to look up an EJB component.
- Contextualized Dependency Lookup (CDL) is similar to dependency pull. Dependencies are looked up against the container managing the resource, not from a central registry.
Dependency injection has three common types:
- Constructor Injection provides the class’s dependencies in its constructor(s). The class declares a constructor or a set of constructors taking its dependencies as arguments. The IoC container passes the dependencies to the class when it instantiates it. Constructor injection is usually used for mandatory dependencies that are required by the class. These are usually immutable dependencies.
- Setter Injection provides the class’s dependencies using JavaBean-style setter methods. Using these setters the IoC container can inject the required dependencies. Setter injection is usually used for optional dependencies. This lets us create mutable/reconfigurable objects which allow for later re-injection.
- Field Injection is done using annotations on fields. The container uses the Reflection API to access the fields directly. The dependency values are injected directly into the fields of the class, as opposed to indirectly set through the constructors or the setters.
Injection vs Lookup
Choosing whether to use injection or lookup is usually very easy. In most cases, it is determined by the container we’re using. For example, when we used J2EE and EJB 2.1, then we were forced to look up an EJB using JNDI. Using EJB3 or Spring, the components and their dependencies are generally wired together using injection.
Which one should we use if we have a choice? Definitely injection!
Using injection has zero impact on our components’ code. When we use injection, the most our classes have to do is allow dependencies to be injected. Dependency pull code, on the other hand, must actively get a reference to the registry and interact with it to look up the dependencies. Using CDL requires our classes to implement specific interfaces and manually look up all dependencies.
With lookup, our classes are always dependent on the container defined interfaces.
Another drawback with lookup is that it is very difficult to test our classes in isolation. Using injection, testing our components is easy. All we need to do is provide the dependencies using the appropriate constructors or setters.
Benefits of Dependency Injection
Benefits of using DI include:
- Reduced glue code: DI dramatically reduces the amount of code we need to write to glue the components of our application together.
- Simplified configuration: DI simplifies application configuration. We can use a variety of options to configure the injectable classes. DI makes it much simpler to swap one implementation of a dependency for another.
- Single dependency repository: When we use the traditional approach to dependency management, we create instances of our dependencies (or look them up from factory classes) where they are needed within the dependent class. This spreads the dependencies across multiple classes. Changing the dependencies becomes problematic. When we use DI, all the common dependencies are generally contained in a single place (a repository or a configuration file). This makes dependency management simpler and less error prone.
- Easier to test: Designing our classes properly with DI in mind, it becomes easier possible to replace or swap out dependencies. This is very useful for mock testing.
- Better application design: Designing for DI means we must design around interfaces. All major components are defined as interfaces, and then concrete implementations of these interfaces are created and wired together using the DI container.
Please share your views and comments. And don’t forget to sign up to get your weekly Java tip.