Spring Dependency Injection and Inversion of Control

A Guide to Spring Dependency Injection and Inversion of Control and examples showing the Spring Dependency injection in action.

Inversion Of Control

Before we start with anything, let’s learn what the Inversion of Control is.
The Inversion of control is a term used in Object Oriented Programming, by which the control of an object or set of objects is given to a framework or a container provided by the framework.

Spring Inversion of Control | amitph

Although the above image intends humour, it describes what Inversion of Control is. If we consider humans as a software component or a service, they are meant to perform actions like waking up, going to meetings, or paying bills. For other things like Keeping track of meetings, to set up alarms or reminders, humans use Phones or any smart devices.

Spring Inversion of Control is similar. We want our software components to do their given jobs. We take configurations and dependencies out of the components and give them to a container called an Inversion of Control Container or IOC Container.

What is a Dependency?

An application is made up of multiple classes. Usually, each class should have its dedicated responsibility. This results in our classes integrating with different classes to complete certain functionality. When a class A calls a method of class B. Class A is dependent on Class B.

Tightly Coupled Objects

Learn how having a dependency can cause Tightly Coupled Objects problems. See the below code.

This is a FileUploadService that grabs a file, checks if the file has one of the expected extensions and asks the FileStorageService to store the file.

public class FileUploadService {

  private List<String> validFiles = 
      List.of("xls", "doc"."txt", "ppt");
  
  private FileStorageService service = 
      new AzureBlobStorageService();

  public FileUploadService() {}
}Code language: Java (java)

The code above uses the Program to Interface principle to instantiate FileStorageService. But still, the respective implementation is hard-coded in the class. Also, it hard-codes the list of valid files. Both of the problems are due to the Tight Coupling.

Loosely Coupled Objects

Let’s update the FileUploadService a bit, and we will get Loosely Coupled objects.

public class FileUploadService {

  private List<String> validFiles;
  private FileStorageService service;
    
  public FileUploadService(
      List<String> validFiles, 
      FileStorageService service){
    this.validFiles = validFiles;
    this.service = service;
  }
}

class User {
  public static void main(String[] ar) {
    List<String> validFiles = 
        List.of("xls", "ppt", "doc");
    FileStorageService service = 
        new AzureBlobStorageService();
        
    FileUploadService fileUploadService = 
        new FileUploadService(validFiles, service);
  }
}
Code language: PHP (php)

Now, the FileUploadService is not tightly coupled to any other specific implementation. That means the class remains unchanged even if the list of valid files is modified or we use a different implementation of the FileUploadService.

Dependency Injection

What we just did is called Dependency Injection.

Dependency Injection is a term used in Object Oriented Programming, by which Objects will focus on doing the assigned functionality and utilizing other objects. The objects will not handle the necessary configurations. However, the Objects will provide a way to initialize them and their dependencies by field assignment, field setters or constructors. This way, external entities can initialize the things, not the actual objects.

In a Spring-based application, the Inversion of the Control Container (IoC container) does the dependency injection. We will see that in the following section. First, let’s see why we even need such a container.

Why do we Need an IoC Container?

I have modified the previous code example. It is now a ResumeUploaderService. A Candidate can share their resume with the ResumeUploaderService. Upon verifying the extension, the service should share it with a ResumeStorageService. As per the organization’s current strategy, the resumes are stored in a confidential folder of the file system (by the FileSystemResumeStorageService).

public class ResumeUploaderService {

  private List<String> validFiles;
  private ResumeStorageService service;

  public ResumeUploaderService(
      List<String> validFiles, 
      ResumeStorageService service) {
    this.validFiles = validFiles;
    this.service = service;
  }
}


class Candidate {
  public static void main(String[] ar) {
    List<String> validFiles = List.of("pdf", "doc");

    String filePath = "/Users/app/confidential/storage/resume";
    ResumeStorageService service = 
        new FileSystemResumeStorageService(filePath);

    ResumeUploaderService fileUploadService = 
        new ResumeUploaderService(validFiles, service);
  }
}
Code language: Java (java)

Although the ResumeUploaderService is perfectly decoupled from the external changes, the Candidate now violates the Single Responsibility Principle. Apart from uploading the resume, it has an additional responsibility of managing ResumeUploaderService‘s dependencies and their instantiation.

This indicates we need something to take care of all the configurations and initializations. Something whose sole responsibility is to manage the initializations.

Inversion of Control Container (IoC Container)

Spring provides an IoC Container to solve the problem. This container instantiates all the objects, and while doing so, it also resolves their dependencies. The class ApplicationContext represents the Spring IOC Container. The Application context is responsible for instantiating, configuring and wiring the beans.
Remember, Beans are nothing but Java objects registered with Spring’s Application Context.

The Application Context needs instructions to configure, instantiate, or write beans. These instructions can be provided in the form of XML configurations, Java Annotations or Code.

Spring Dependency Injection

In Spring, each object is a bean. Each object has an id and name. An ApplicationContext keeps track of all such beans, their names, and ids. When a consumer requests a bean, the Application Context returns an instance of the bean. Look at the below code to understand bean creation and wiring in detail.

Below example is based on Spring Constructor Injection, which injects the dependencies through the object constructor. Two more ways of dependency injections are Spring Field Injection and Spring Setter Injection. In practice, we can use any of these injection types; however, there are some pros and cons. Hence it is important to understand the difference between Setter Vs Field Vs Constructor Injection.

Spring Dependency Injection | amitph
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("resumeStorageService")
public class FileSystemResumeStorageService 
    implements ResumeStorageService {

  @Value("${resume.storage.path}")
  private String storagePath;            
  //Skipped
}
Code language: Java (java)

The @Component annotation requests Spring to register this class as a bean by the specified name. If the name is not provided, Spring uses the Class name.

With the @Value annotation, Spring reads the storage path from Application Properties, Environment Variables, or Program Arguments and initializes the instance field with the value.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class ResumeUploaderService {

  @Autowired
  @Qualifier("resumeStorageService")
  private ResumeStorageService storageService;


  public ResumeUploaderService(
    ResumeStorageService storageService) {
    this.storageService = storageService;
  }

  // Skipped
}
Code language: Java (java)

The ResumeUploaderService uses the @Qualifier annotation to specify the bean name it wants to inject into the instance variable. To inject a different implementation of the ResumeStorageService, we only need to name the bean ‘resumeStorageService’.

import org.springframework.beans.factory.annotation.Autowired;

public class Candidate {
  @Autowired 
  private ResumeUploaderService resumeUploaderService;

  public void upload(Byte[] resume) {
    resumeUploaderService.uploadResume(resume);
  }
}
Code language: Java (java)

We can see that all the components are loosely coupled and focused. Also, they are free from instantiating other classes and managing their dependencies.

Summary

This article provided a practical guide to Spring’s Dependency Injection and Inversion of the Control mechanism.

We first understood class dependencies and how they cause tightly-coupled components. We wrote an example of interdependent classes and then used Spring’s dependency injection to make them loosely coupled and focused.


2 thoughts on “Spring Dependency Injection and Inversion of Control

Comments are closed.