Inversion of Control (IoC), also known as Dependency Injection (DI), allows an object to define their dependencies as constructor arguments (strictly speaking, you can set these dependencies as properties, but the examples I will use today are constructor-based). This is the inverse of the object itself controlling the instantiation or location of its dependencies, hence the name Inversion of Control.
Let’s look at an example from Stackoverflow using a text editor with a spell checking component:
public class TextEditor {
private SpellChecker checker;
public TextEditor() {
this.checker = new SpellChecker();
}
}
This code creates a dependency between the TextEditor
and the SpellChecker
.
Inversion of Control removes this dependency by injecting the SpellChecker
into the constructor of the TextEditor
public class TextEditor {
private SpellChecker checker;
public TextEditor(SpellChecker checker) {
this.checker = checker;
}
}
The second example introduces an abstraction between TextEditor
and
SpellChecker
, allowing us to create the dependency and then inject it into the
class where it is used.
SpellChecker sc = new SpellChecker;
TextEditor textEditor = new TextEditor(sc);
The example I went through is not particularly interesting, being, as it were,
an example. Yet it unlocks some interesting benefits. First, it provides a clean
abstraction point where we can change the implementation of the spell checker
without having to change the text editor itself. For example, if we change
SpellChecker
to an interface, we can then instantiate different spell checkers
and leverage them in the text editor. This turns our TextEditor
class into a
reusable component that we can leverage with multiple different spell checkers
depending on our needs. A second benefit is that this pattern makes it easy to
follow the Single-Responsibility
Principle by
making explicit the interfaces and dependencies between objects. Lastly,
projects that leverage dependency injection are typically easier to test by
making dependencies easily mockable without having to reach into the inner
workings of a class.
Dependency Injection with Spring
The Spring Framework was one of the first to use dependency injection in Java using Spring’s Inversion of Control (IoC) container. Spring takes the basic dependency injection pattern I just covered and adds some additional features like Aspect Oriented Programming support, internationalization, event publication, injection through configuration, and support for web applications, but we will focus solely on dependency injection today. Other popular dependency injection frameworks include Guice and Dagger.
The Spring Framework divides the dependency injection functionality into two parts: the IoC container and Beans. In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is just a fancy name for an object and we use the nomenclature bean to signify that the Spring IoC container will manage that object. Beans, and the dependencies between them, are registered with Spring using XML-based configuration or, more recently, Java annotations. Once registered, the Spring IoC container is responsible for instantiating, configuring, and assembling the beans by reading the configuration metadata you have supplied.
The ApplicationContext
is Springs IoC container. It is the interface to an
advanced factory capable of maintaining a registry of different beans and their
dependencies. The job of the ApplicationContext
is to read bean definitions,
assemble them, and provide access to them. In our example above, to set the
checker
attribute in our instantiated TextEditor
class, we’ve structured our
class to do constructor-based dependency injection by declaring dependencies as
explicit constructor arguments. To connect this constructor to the Spring
ApplicationContext
we can use Java annotations. For example, we can provide
some Spring configuration using a class with the following annotations:
@Configuration
public class AppConfig {
@Bean
public SpellChecker checker() {
return new TextFileSpellChecker();
}
@Bean
public TextEditor editor() {
return new TextEditor(checker());
}
}
The @Configuration
annotation indicates that the class is a source of bean
definitions. The @Bean
annotation is used on a method to define a bean. By
separating this configuration from the code, we have a reusable component. If
another application developer needs a text editor component, they can declare
their own configuration with a different implementation. In the following
example, we instruct Spring to use a database-backed implementation of the spell
checker by using a different spell checker implementation in our configuration.
This allows the TextEditor
to remain unchanged and provides a clear
integration point for users of the TextEditor
.
@Configuration
public class AppConfig {
@Bean
public SpellChecker checker() {
return new DatabaseSpellChecker();
}
@Bean
public TextEditor editor() {
return new TextEditor(checker());
}
}
When the ApplicationContext
is created, it is initialized with the
configuration metadata describing all beans in the application. For each bean,
its dependencies are expressed in the form of constructor arguments. These
dependencies will be provided by the ApplicationContext
to the bean when the
bean is actually created.
Although getting setup with a dependency injection framework like Spring (or Guice, or Dagger) involves a bit of work, the advantages of this architecture are:
- decoupling the execution of a task from its implementation
- making it easier to switch between different implementations
- greater modularity of a program
- greater ease in testing a program by isolating a component
As programs get more complex, these advantages become more and more important in allowing developers to iterate quickly over large codebases.