Spring Boot Tutorial

Spring Boot - Software Setup and Configuration (STS/Eclipse/IntelliJ)

Prerequisite (Spring Core Concepts)

Spring Boot Core

Spring Boot with REST API

Spring Boot with Database and Data JPA

Spring Boot with Kafka

Spring Boot with AOP

Inversion of Control with Example

Inversion of Control (IoC) is a design principle where the control of object creation and its lifecycle is transferred from the application's main routine to an external container or framework. The purpose of IoC is to invert the flow of control compared to traditional control flow. Instead of the application code deciding when and how to create an object, the "container" makes those decisions.

This decoupling allows for more modular code, easier testing, and better separation of concerns. Dependency Injection (DI) is a popular method to achieve IoC, where objects receive their dependencies from an external source rather than creating them internally.

Example:

Imagine a classic scenario where you have an application that sends notifications. Without IoC, you might have:

public class EmailService {
    public void sendEmail(String message, String receiver){
        // logic to send email
    }
}

public class Notification {
    private EmailService emailService = new EmailService();
    
    public void notifyUser(String message, String receiver) {
        emailService.sendEmail(message, receiver);
    }
}

In the above example, Notification class directly instantiates EmailService. This makes it hard to change the notification mechanism in the future (e.g., switch to SMS or a messaging app).

Now, let's implement this with IoC using DI:

public interface MessageService {
    void sendMessage(String msg, String receiver);
}

public class EmailServiceImpl implements MessageService {
    public void sendMessage(String msg, String receiver){
        // logic to send email
    }
}

public class SMSServiceImpl implements MessageService {
    public void sendMessage(String msg, String receiver){
        // logic to send SMS
    }
}

public class Notification {
    private MessageService service;
    
    public Notification(MessageService svc){
        this.service = svc;
    }
    
    public void notifyUser(String message, String receiver) {
        service.sendMessage(message, receiver);
    }
}

In this refactored code:

  1. We've defined a MessageService interface.
  2. We have two implementations of the MessageService: EmailServiceImpl (sends emails) and SMSServiceImpl (sends SMS).
  3. Notification class now takes a MessageService as a constructor argument (Dependency Injection).

When you want to send a notification via email:

MessageService emailService = new EmailServiceImpl();
Notification notification = new Notification(emailService);
notification.notifyUser("Hello", "user@example.com");

Or if you want to send a notification via SMS:

MessageService smsService = new SMSServiceImpl();
Notification notification = new Notification(smsService);
notification.notifyUser("Hello", "1234567890");

With IoC in place, the Notification class doesn't need to be changed if you introduce a new method of sending messages. All you'd need is a new implementation of MessageService.

In real-world applications, frameworks like Spring or Java EE provide the IoC container, and you don't manually instantiate the classes and inject dependencies like in the above example. Instead, the container manages object creation, lifecycle, and dependency injection.

1. Inversion of Control explained with examples:

Inversion of Control (IoC) is a design principle where the control flow of a program is inverted. Instead of the developer controlling the flow, a framework or container takes control. This enhances modularity and flexibility.

// Example without IoC
public class WithoutIoC {
    public static void main(String[] args) {
        Service service = new Service();
        service.doSomething();
    }
}
// Example with IoC
public class WithIoC {
    private Service service;

    public WithIoC(Service service) {
        this.service = service;
    }

    public void execute() {
        service.doSomething();
    }
}

2. IoC design pattern with practical examples:

IoC is implemented through design patterns like Dependency Injection. It helps in creating loosely coupled components.

// Dependency Injection example
public class Client {
    private Service service;

    // Constructor Injection
    public Client(Service service) {
        this.service = service;
    }

    public void execute() {
        service.doSomething();
    }
}

3. IoC container usage in Java applications:

IoC containers manage object creation, instantiation, and dependency injection. In Java, frameworks like Spring provide IoC containers.

// Spring IoC container example
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Client client = context.getBean(Client.class);
client.execute();

4. IoC vs. Dependency Injection: Examples and differences:

IoC is a broader concept, while Dependency Injection is a specific implementation of IoC. IoC focuses on inverting the control flow, while DI deals with how components get their dependencies.

// IoC example
public class IoCExample {
    private Service service;

    public IoCExample(Service service) {
        this.service = service;
    }

    public void execute() {
        service.doSomething();
    }
}
// Dependency Injection example
public class DIExample {
    private Service service;

    @Autowired
    public void setService(Service service) {
        this.service = service;
    }

    public void execute() {
        service.doSomething();
    }
}

5. IoC container implementation in Spring framework:

Spring IoC container manages the lifecycle of objects and injects dependencies.

<!-- beans.xml -->
<beans>
    <bean id="service" class="com.example.ServiceImpl"/>
    <bean id="client" class="com.example.Client">
        <property name="service" ref="service"/>
    </bean>
</beans>

6. IoC in modern software development with code samples:

IoC is a crucial concept in modern software development, promoting modular, maintainable, and testable code.

// Modern IoC example with Spring Boot
@Service
public class ModernService {
    public void doSomething() {
        // Implementation
    }
}
// Modern IoC usage in a Spring Boot controller
@RestController
public class ModernController {
    @Autowired
    private ModernService service;

    @GetMapping("/doSomething")
    public void handleRequest() {
        service.doSomething();
    }