SpringTechnology

Spring Dependency Injection and Inversion of Control

Learn the concepts of Dependency Injection and Inversion of Control and then look into how the Spring Framework supports them with the help of code examples.

Inversion Of Control

Before we start off 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 is Inversion of Control. If we consider humans as a software component or a service, they are meant to perform the actions like wake up, go to meeting, or pay bills. For the 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 as Inversion of Control Container or IOC Container.
More to come in the below sections.

What is a Dependency ?

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

Tightly Coupled Objects

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

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

public class FileUploadService {

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

    public FileUploadService() {}

    //
    // Other methods
    //
}

In the code above, we are using Program to Interface principle to instantiate FileStorageService. But still, the respective implementation is hard-coded in the class. Also the validFiles is hard-coded. Both of them are causing a Tightly Coupled objects.

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 = Arrays.asList("xls", "ppt", "doc");
        FileStorageService service = new AzureBlobStorageService();
        
        FileUploadService fileUploadService = new FileUploadService(validFiles, service);
    }
}
  • Line #3: The variable is declared and not initialised. No hardcoded value.
  • Line #4: Only a reference to FileStorageService type. No implementation attached.
  • Line #6: All arguments constructor.

Let’s see what is happening in User class, which is actually the user of FileUploadService.

  • Line #17: The FileUploadService instance is created by passing all the required arguments to constructor.

Dependency Injection

What we just did is called as Dependency Injection.

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

In a Spring based application, Inversion of Control Container (IoC container) does the dependency injection. We will see that in coming section. First, let’s see why do 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 its resume to the ResumeUploaderService. The service should, upon verifying the extension, share it to a ResumeStorageService. As per the organisations current strategy the resumes are being stored in a confidential folder of file system (by 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 = Arrays.asList("pdf", "doc");

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

        ResumeUploaderService fileUploadService = new ResumeUploaderService(validFiles, service);
    }
}
  • Line #4: ResumeUploaderService has-a reference to ResumeStorageService.
  • Line #6: Constructor which accepts and sets an implementation of ResumeStorageService.

To upload a resume the Candidate has to instantiate ResumeUploaderService and pass resume. But with all the dependency injection thing, Candidate’s job has become difficult. Candidate will not only have to instantiate ResumeUploaderService but also ResumeStorageService. Because, the former cannot be instantiated without the later.

  • Line #17: The candidate decides where to store the resume (I know..its funny !!)
  • Line #18: Candidate decides whether to use FileSystemResumeStorageService or AzureBlobStorageService.
  • Line #20: Finally, candidate instantiates the ResumeUploaderService.

Below are the important issues with above

  • Consumer knows too much.
  • Consumer, instead of using the service, also initialising it.
  • Consumer shouldn’t worry how ResumeUploaderService doing its job (lack of abstraction).
  • The ultimate-end consumer, we have to know everything and will have to initialise everything in the system.

This clearly indicates, we need something that can take care of all the configurations and initialisations. Something, whose sole responsibility is to manage the initialisations.

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.

To configure, instantiate or write beans the Application Context needs some instructions. 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, or name. An ApplicationContext keeps track of all such beens and ids. When a bean is requested, by a consumer, 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 object constructor. There are two more ways of dependency injections called as Spring Field Injection and Spring Setter Injection. In practice you can use any of these injection types however there are some pros and cons. Hence it is important to understand Difference in 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;             // Storage path assigned based on properties file

    //
    // Skipped methods
    //
}
  • Line #4: Tells Spring to register this class as a Bean and identify it by the given name. If name is not provided the class name is considered as identifier.
  • Line #8: The storage path is now directly injected from a properties file. No need for a consumer to pass it.
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 methods
    //
}
  • Line #5: Declares the class as a Spring Bean and class name as identifier.
  • Line #10: Tells spring to Auto Wire the ResumeStorageService implementation which is identified by "resumeStorageService".

If we want to attach a different implementation of ResumeStorageService the ResumeUploaderService is not going to change at all.

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

public class Candidate {
    @Autowired private ResumeUploaderService resumeUploaderService;

    public void upload(Byte[] resume) {
        resumeUploaderService.uploadResume(resume);
    }
}
  • Line #4: Asks Spring to assign an instance of resumeUploaderService.

Everything is so clean and focused. No class is initialising another class or setting any configuration for another class. Everything is managed by Spring’s Inversion of Control Container (IoC Container).

Summary

You have come to an end of of the Spring Dependency Injection and Inversion of Control guide. You learnt what is Dependency and how classes can be Tightly Coupled or Loosely Coupled. We understood the concepts of Dependency Injection and Inversion Of Control in an Object Oriented Programming. You also learnt that Spring‘s Inversion of Control Container (IoC Container) manages all the Dependency Injections in our Spring Application.

There are still plenty of things to learn about Springs Dependency Injection. We will cover them in sub ubsequent tutorials.


2 thoughts on “Spring Dependency Injection and Inversion of Control

Comments are closed.