Open In App

Java Dependency Injection (DI) Design Pattern

Last Updated : 13 Feb, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

The dependency Injection Design Pattern in Java, is part of Design Patterns, let suppose that you have a company, that companies dependent on the employees, and the employees is dependent on the company. So here company will be the dependency of the employee, and employee dependency is the company. So this is Dependency Injection.

Java-Dependency-Injection-Design-Pattern

What is the Dependency Injection Design Pattern in Java?

jd-1

The Dependency Injection (DI) design pattern in Java is a powerful technique for managing dependencies between objects. It aims to decouple classes from their dependencies, making code more flexible, testable, and maintainable.

  • Dependency Injection revolves around a simple process of dependencies being injected through an external source.
  • In Object Oriented Paradigm dependencies can be considered as those entities which provide a certain functionality.

How Dependency Injection Design Patterns in Java work?

jd-3-copy

  • Identify dependencies:
    • Analyze the classes in your application to determine which objects they rely on to function. These objects are their dependencies.
    • Define clear interfaces or abstract classes to represent these dependencies, promoting loose coupling.
  • Inject dependencies:
    • Constructor Injection: Pass the dependencies as arguments to the class’s constructor. This makes them mandatory for object creation and ensures immutability.
    • Setter Injection: Provide setter methods to set the dependencies after object creation. This offers more flexibility but can make the class mutable.
  • Inversion of control(IOC): 
    • Employ an IoC container or framework to manage the creation and injection of dependencies. This inverts the control flow, as classes no longer create their own dependencies but receive them from the container.
    • Popular IoC containers in Java include Spring, Guice, and PicoContainer.

Example of Dependency Injection Design Patterns in Java

Problem Statement

Consider any vehicle. Let us say, this vehicle is a car. For this car to function smoothly we have various components like the car’s engine, the car’s headlights & many such components that are assembled to build the car.

jd-4

These components can be considered as your dependencies. Without any of these dependencies, our car can never be considered as a complete entity.

In the above image,

  • We have a set of dependencies, these dependencies can be the Engine of a vehicle. There maybe more such dependencies present. These dependencies are handed over to an external agent, in this case, the external agent is the Spring framework.
  • This external agent, that is the Spring framework will take over the responsibility of injecting these dependencies into the respective Car, Bike and Scooter classess, as depicted in the above image.
  • These dependencies are the instance variables of your Java classes that get injected into our targeted classes through the external agent that is taking control of this process.

Why do we need to inject dependencies through some external agent?

When you inject dependencies through an external agent, your code becomes less tightly coupled to the specific implementations of those dependencies.

  • We can easily swap out different implementations of the same dependency without having to modify your main code. This makes your code more modular and easier to maintain.
  • Ultimately, the best way to decide whether or not to use dependency injection is to consider the specific needs of your application.
  • If you are writing code that is likely to change or that needs to be easily tested and reused, then dependency injection can be a valuable tool.

Step wise Step Implementation for the above Problem

In the below solution we will understand above problem using code:

Now let us assume, our car is fitted with the most branded engine model of a certain company. Consider this car as your Java class containing its respective engine model as a dependency.

Java




public class Car
{
    /** Car containing engine as its dependency **/
    public Engine engine = new LegacyEngine();
}


The class Car has an instance variable Engine . Engine is an interface that is implemented by the respective class LegacyEngine.This instance variable Engine is initialized to a new object as seen above.

Java




public interface Engine
{
    /** The Engine interface having methods for the engine to work**/
}


As we see in the above code snippet, this Engine interface is a specification used here. This interface will be implemented by the respective classes that will override the abstract methods of this interface providing the appropriate functionalities.

Java




public class LegacyEngine implements Engine
{
 
}


Now in the above code snippet, the Engine interface gets implemented by the LegacyEngine class. Assume the interface has some abstract methods that are being overridden by this implementing LegacyEngine class.

Real-world problem that occurs with Java Dependency Injection Design Pattern

jd-5

However, now in the future this branded engine model gets deprecated. Now we wish to have our car fitted with a newer engine model brand (NewEngine) to achieve newer functionalities that were missing with our current engine model used.

Java




public class NewEngine implements Engine
{
  /** Newer Engine Model available **/
}


In the above code,

  • This NewEngine class is another class that implements the Engine interface.
  • Now we want this new engine model, that is the object of this NewEngine class to be used in our codebase instead of the older LegacyEngine class.

Replacing Current Engine with a new Engine

Replacing this dependency with a newer version leads to tight coupling of our codebase.i.e., Changing the code accordingly: –

Java




public class Car
{
     /** replacing the old engine dependency with the new engine dependency
         public Engine engine = new LegacyEngine();
     **/
     public Engine engine = new NewEngine();
}


As seen in the above code snippet,

  • We require to replace the Engine interface with a new class that implements it. That is, we need to replace this older object that was initialized for this interface Engine with the NewEngine object.
  • The process is tedious just as it may seem in the real world too. Reason being, to replace this dependency with some other dependency, we need to instantiate the current engine’s object to a new object.

Note:

Now imagine, we have this old engine model fitted in hundreds of such vehicles. Not just in a car but also the same engine model maybe fitted inside a bike/scooter. We need to modify the code base in these 100 classes, replacing them with the new engine object.

This same process would have to be repeated 100 times for those 100 Java classes we want to change. This is a problem that needs to be solved.

Solution to solve this problem for all the vehicles, Bike, Scooter:

To solve this problem, we use the following step:

  • This is where the Spring Framework, comes to our rescue. With Spring’s Inversion of Control (IOC) feature, the targeted Java classes no longer take the responsibility of initializing its instance variables.
  • This job is taken care of by the Spring Framework through its @Configuration annotated classes.

The solution to solve this Problem by using Spring Boot:

jd-6

Code Implementation for the above Problem:

We will make use of the Spring’s @Autowired annotation that will enable the instance variables of our classes to be initialized through the Spring framework at run time of our application, without the need for us to manually hard code it.

Java




import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class BeanConfig
{
     
    @Bean
    public Engine getEngineBean()
    {
        /** Changing the old engine to the new engine dependency
              Engine engine = new LegacyEngine();
         **/
          
        Engine engine = new NewEngine();
        return engine;
    }
 
}


In the above code snippet:

  • @Configuration annotation denotes the BeanConfig class as the Spring’s Configuration class. This is the class into which we will configure and define all the beans that will be used by our application.
  • @Bean annotation marks the result returned by the getEngineBean() method to be initialized for the instance variable Engine interface used by our application code.
  • Inside the getEngineBean() method, we replace the Engine object that was previously initialized to the LegacyEngine() object changing it to the NewEngine() object.

Java




import org.springframework.beans.factory.annotation.Autowired;
 
public class Car
{
     /** Using the @Autowired annotation to intialize the
         instance variable Engine from Spring's
         Configuration Class
     **/
     @Autowired
     public Engine engine;
      
}


In the above code snippet:

  • The class Car consists of an instance variable, that is its dependency, which is Engine.
  • This Engine dependency is initialized to its respective object configured in the @Configuration annotated class handled by the Spring framework.
  • This initialization of the Engine dependency to its respective object that is configured takes place by placing the @Autowired annotation above the given field (Engine) that will be autowired from the BeanConfig class.

Similarly, this Engine Dependency can be autowired in the same way for the remaining Bike, Scooter classes below:

Java




import org.springframework.beans.factory.annotation.Autowired;
 
public class Bike
{
      @Autowired
    Engine engine;
   
}


In the above code snippet:

  • The class Bike consists of an instance variable, that is its dependency, which is Engine.
  • This Engine dependency is initialized to its respective object configured in the @Configuration annotated class handled by the Spring framework.
  • This initialization of the Engine dependency to its respective object that is configured takes place by placing the @Autowired annotation above the given field (Engine) that will be autowired from the BeanConfig class.

Java




import org.springframework.beans.factory.annotation.Autowired;
 
public class Scooter
{
    @Autowired
    Engine engine;
 
}


In the above code snippet:

  • The class Scooter consists of an instance variable, that is its dependency, which is Engine.
  • This Engine dependency is initialized to its respective object configured in the @Configuration annotated class handled by the Spring framework.
  • This initialization of the Engine dependency to its respective object that is configured takes place by placing the @Autowired annotation above the given field (Engine) that will be autowired from the BeanConfig class.

The pom.xml file containing the Spring MVC dependency:

XML




 
  <dependencies>
        <dependency>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-webmvc</artifactId>
                    <version>5.2.2.RELEASE</version>
        </dependency>
  </dependencies>
   
</project>


The pom.xml file consists of the Spring Web MVC dependency that provides us with the @Configuration, @Autowired, @Bean annotations to achieve the process of dependency injection in our above example.

As we seen from the above example,

  • We did not have to change a lot of lines of code to achieve our above requirement.
  • All we did was relied on the Spring Framework’s Inversion of Control feature to achieve the dependency injection design pattern across our application, thereby making our code base loosely coupled and more reusable than before.

Advantages of using Java Dependency Injection Design Pattern

  • The Dependency Injection design pattern enables us to reuse and modify our code base in the future, without requiring much effort to do so.
  • With dependency injection in place, we achieve loose coupling between our classes. This loose coupling offers various benefits in terms of enabling our developed code to accommodate new code changes easily.
  • One of the major advantages of employing the Dependency Injection design pattern is that it enables us to Unit Test our code using the Junit & Mockito Framework. This is done by creating mock objects of the dependencies for testing purposes.
  • Dependency Injection offers flexibility to the targeted classes by using the feature of Inversion of Control, where the responsibility of initializing the instance variables of those targeted classes is managed by the external Spring framework.

Disadvantages of using Java Dependency Injection Design Pattern

  • Every new dependency, that is for the new instance variables that would be added in the future. For such dependencies, we have the overhead of ensuring that they must adhere to the dependency injection design pattern.
  • Because of dependency injection, we may introduce additional complexity in terms of managing the external @Configuration annotated classes that is responsible for the creation and destruction of the container beans as per the respective bean scopes defined.
  • There is a high possibility that when employing the Dependency Injection design pattern, the dependencies could be inappropriately configured, due to a duplicate bean exception or a failure to autowire such dependencies or any other such similar exceptions that could be encountered.
  • It is not always possible to leverage the dependency injection design pattern all the time. There could be situations where one maybe dealing with legacy code base systems, that might have been developed in the absence of the Spring Framework.



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads