Inversion of Control, Dependency Injection, and the Spring IoC Container

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:

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
7
8
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.

1
2
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.

assets/container-magic.png

The Spring IoC Container converts business objects and configuration into a running application.

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@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.

java  spring  ioc 

See also

comments powered by Disqus