Tuesday, December 20, 2022

Dependency Injection with Inversion of Control in Spring Boot

spring framework,spring boot,java,hibernate,jpa,programming,software development,technology
This tutorial discusses Dependency Injection (DI) in Spring Boot and how DI can be achieved. But before that, we will know what is Inversion of Control (IoC) and the relation between the Inversion of Control and Dependency Injection (DI).

1. What is Inversion of Control (IoC)

Inversion of Control (Ioc) is a Design Principle that is often used in the Object Oriented Programming (OOP) context. The principle of IoC is that the interdependency between/among the classes should be minimal - which means we can easily plug or unplug any dependent entities without affecting the actual control flow (which means the classes should be loosely coupled). To understand this concept let’s consider the following examples:

Example #1

public class MySQLDb {
    public String connect(String connectionURL) {
        System.out.println(“Connection URL :: ” + connectionURL);
    }
}

public class MyApplication {
    MySQLDb dbConnection = new JavaDeveloper();
    dbConnection.connect(“db:mysql”)
}
We have created a MySQLDb class with the connect() procedure. In the MyApplication, we create an instance of MySQLDb and call connect().

Example #2

public interface IDbConnection {
    public String connect(String connectionURL);
}

public class MySQLDb implements IDbConnection {
    @override
    public String connect(String connectionURL) {
        System.out.println(“Connection URL :: ” + connectionURL);
    }
}

public class OracleDb implements IDbConnection {
    @override
    public String connect(String connectionURL) {
        System.out.println(“Connection URL :: ” + connectionURL);
    }
}

public class MyApplication {
    IDbConnection dbConnection = new MySQLDb();
    dbConnection.connect(“db:mysql”);

    dbConnection = new OracleDb();
    dbConnection.connect(“db:oracle”);
}
Here MySQLDb and OracleDb implement the IDbConnection interface - and each class overrides the connect() procedure in their way. In MyApplication, we have created instances of MySQLDb and OracleDb and assigned them to the generic IDbConnection interface.

Observations

Example #1

  • MyApplication can only use MySQLDb. So these classes are tightly coupled - which means dependency between them is in higher order.
  • If MyApplication wants to connect to any other database, we need to change the entire code.
  • Here MyApplication entirely controls the flow of MySQLDb dependency.

Example #2

  • We can easily plug any type of database connectivity (MySQL DB or OracleDB) into MyApplication. So these classes are loosely coupled - which means dependency between them is low.
  • As we can pass the connectionURL, the database server can easily be changed.
  • Here MyApplication does not entirely control the flow of MySQLDb/OracleDb dependency - the control is now to the user of the MyApplication.

So, with Example #1, users or any external entity can not change the control flow of MyApplication. On the other hand, users or external entities can modify the control flow of MyApplication in Example #2 - the control flow of MyApplication is inverted, which means an external entity or service or any framework can control the flow as well as the dependencies. This is called Inversion of Control (IoC).

A perfect example of the implementation of Inversion of Control (IoC) is the Spring Framework. The org.springframework.beans and org.springframework.context packages are the basis for Spring Framework's IoC container. The BeanFactory is a primary component of the core IoC container and the ApplicationContext adds more enterprise-specific functionality. Objects are the backbone of any Spring Application. In Spring Framework, they are managed by the IoC Containers - they are called beans. So, simply a bean is one of the many objects in our application.

Inversion of Control (IoC) can be achieved in various ways, such as Strategy design pattern, Service Locator pattern, Factory pattern, and Dependency Injection (DI).

2. What is Dependency Injection (DI)

Dependency Injection (DI) is the implementation of IoC. So without IoC principle, Dependency Injection is not possible. Let’s understand the dependency injection with this example: suppose object Car requires a method of object Engine to initiate the engine start process. So, dependency injection suggests that instead of creating an instance of class Engine in class Car using the new operator, the object of class Engine should be injected in class Car.

Just take a look at the following code snippet for a clear understanding:
public class Engine {
    public void startEngine() {
        System.out.println(“Initial engine start process…”);
    }
}

public class Car {
    private Engine engine;
    public Car(Engine engine) {
        This.engine = engine;
    }
}

So, in dependency injection object creation is done by the Spring IoC containers rather than the application itself. It reduces coupling between multiple objects as it is dynamically injected by the Spring Framework.

In summary: Spring Framework has implemented Inversion Of Control (IoC) via IoC Container. And IoC Container helps to achieve Dependency Injection (DI).

3. Injection Type

Spring framework mainly supports two forms of Dependency Injection:
  • Constructor Injection: In the constructor-based dependency injection, we inject the dependent objects through its constructor argument at the time of initializing it. This way we can declare all dependencies in one step. This is the best practice to inject the dependencies.
  • Setter Injection: In setter injection, dependencies are injected via the setter methods. The IoC container will use these setter methods to allocate the dependencies at run-time after invoking a no-argument constructor or no-argument static factory method to instantiate their bean.

In this tutorial, we will examine annotation-based Dependency Injection and use  @Autowired annotation to implement DI in Spring Boot. 

First, we will implement the constructor-based dependency injection and then look into the setter-based dependency injection.

4. Spring Boot project creation

To create a new Spring Boot project, we will use Spring Initializr (https://start.spring.io/), generating a basic structure for our Spring Boot project. We have added the following dependencies:
  • Spring Boot DevTools - necessary development tools
JAVA,Dependency Injection,Autowired,Inversion of Control,Spring Framework,Spring Boot,
Then click on GENERATE to download the project zip file. Unzip the zip file. Now import the project in Eclipse/STS as a Maven project.

5. Constructor Injection

For constructor injection, the IoC container will invoke the parameterized constructor of the class to inject dependencies.

To implement this injection, we use the relationship between Car and Engine as our example to this DI - cars are dependent on engines, so inject the engine dependency in the car.

We have created an interface IEngineService which contains a method getEngineDetails():
public interface IEngineService {
    public String getEngineDetails();
}

This IEngineService interface has been implemented by the EngineServiceImpl class:
@Component
public class EngineServiceImpl implements IEngineService{

    @Override
    public String getEngineDetails() {
        return "All alloy quad overhead cam, 4.0 litre twin turbo V8.";
    }
}
So, EngineServiceImpl overrides the getEngineDetails() method and this method returns a description of the engine. This class is annotated with @Component annotation. By this annotation, we are making EngineServiceImpl a custom bean and EngineServiceImpl is now eligible to be automatically detected by the Spring IoC container. Spring will initiate it and inject it whenever needed. So this bean will be managed by IoC container.

We have another interface ICar which contains two methods: getCarDetails() and getEngine().
public interface ICar {
    public String getCarDetails();
    public String getEngine();
}

The ICar interface is implemented by the AstonMartin class:
@Component
public class AstonMartin implements ICar {

    private IEngineService engine;

    @Autowired
    public AstonMartin(IEngineService engine) {
        this.engine = engine;
    }

    @Override
    public String getCarDetails() {
        return "V8 Roadster";
    }

    @Override
    public String getEngine() {
        return this.engine.getEngineDetails();
    }
}
Here we have declared a private field for the IEngineService, and used it inside the getEngine() method to getEngineDetails().

In the AstonMartin class constructor, we passed IEngineService as an argument and assigned it to the private field. We have also annotated the constructor with @Autowired annotation.

@Autowired annotation allows IoC containers to resolve dependencies automatically by injecting the beans that have been defined. As we are wiring the constructor, Spring Framework will inject the beans with the same type as the constructor arguments. This is Constructor Injection.

Now Spring will scan for the component that implements IEngineService interface, in this case, it is EngineServiceImpl. Spring will create an instance of EngineServiceImpl and inject it into AstonMartin.


In the main method, we are acquiring the bean (AstonMartin) from the ApplicationContext and calling the methods: getCarDetails() and getEngine().
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringbootdiApplication.class, args);
	System.out.println("\nApplication started...");

	System.out.println("\n*** Beans ****");
	System.out.println("\nNo of Beans :: " + context.getBeanDefinitionCount());
	System.out.println("\nBean names ::");
	int count = 0;
	for (String s : context.getBeanDefinitionNames()) {
		System.out.println("Name #" + (count + 1) + " : " + s);
		count++;
	}
	System.out.println();

	// GET BEAN FROM SPRING CONTAINER
	ICar astonMartin = context.getBean(AstonMartin.class);

	// CALL A METHOD ON THE BEAN
	System.out.println(astonMartin.getCarDetails());

	// CALL METHOD TO GET ENGINE DETAILS
	System.out.println(astonMartin.getEngine());
}

Now run the application and inspect the audit log. Along with the many beans, which are automatically loaded by the Spring Framework, we can see our custom beans: astonMartin and engineServiceImpl.
JAVA,Dependency Injection,Autowired,Inversion of Control,Spring Framework,Spring Boot,

And also the outputs of the bean methods:
JAVA,Dependency Injection,Autowired,Inversion of Control,Spring Framework,Spring Boot,

So this is all about Constructor Injection.

6. Setter Injection

In this case, the IoC container will invoke the setter methods of the class to inject required dependencies.

To implement this DI, we will use the previous example of Car and Engine and will inject IEngineService through the setter method. Let's do it.

So we have modified the AstonMartin class - the only difference is that we have wired a setter method, setEngineService(), to inject dependency.
@Component
public class AstonMartin implements ICar {

    private IEngineService engine;

    // @Autowired
    // public AstonMartin(IEngineService engine) {
    //     this.engine = engine;
    // }

    @Autowired
    public void setEngineService(IEngineService engine) {
        this.engine = engine;
    }

    @Override
    public String getCarDetails() {
        return "V8 Roadster";
    }

    @Override
    public String getEngine() {
        return this.engine.getEngineDetails();
    }
}
As we are auto-wiring a setter method, Spring will inject the beans with the same type as the method arguments. This is all about Setter Injection.

7. Conclusion

So, we have learned how Spring Boot manages Inversion of Control and the procedures by which we can achieve Dependency Injection in Spring Boot.

You can download the source code.
Happy coding!!! 😊
in

No comments:

Post a Comment

Popular posts