Dependency Injection (DI) is a design pattern that allows you to manage object dependencies externally, promoting loose coupling and making your code more testable. In the context of Spring Boot, DI is facilitated by the Spring Framework's powerful IoC (Inversion of Control) container. This tutorial will guide you through the fundamentals of Dependency Injection with Autowiring in Spring Boot, including real-world examples and best practices.
Dependency Injection is a design pattern where an object receives its dependencies from external sources rather than creating them internally. This decouples the creation of objects from their usage, making the code more modular and easier to test.
Spring Boot simplifies the process of dependency injection with its autowiring feature, which automatically resolves and injects dependencies.
Autowiring is the mechanism by which Spring manages object dependencies. When a bean is created, Spring scans the application context for other beans that match the required type or name and injects them into the dependent bean.
Before diving into DI and Autowiring, let's set up a basic Spring Boot project.
You can create a new Spring Boot project using Spring Initializr (https://start.spring.io/). Choose the following options:
Add the following dependencies:
Click "Generate" to download the project zip file. Extract it and import it into your favorite IDE (e.g., IntelliJ IDEA, Eclipse).
Create a new package com.example.dependencyinjectiondemo.service and add a simple service class.
package com.example.dependencyinjectiondemo.service;
import org.springframework.stereotype.Service;
@Service
public class GreetingService {
public String greet(String name) {
return "Hello, " + name + "!";
}
}
Create a new package com.example.dependencyinjectiondemo.controller and add a controller that uses the GreetingService.
package com.example.dependencyinjectiondemo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.dependencyinjectiondemo.service.GreetingService;
@RestController
public class GreetingController {
private final GreetingService greetingService;
@Autowired
public GreetingController(GreetingService greetingService) {
this.greetingService = greetingService;
}
@GetMapping("/greet")
public String greet(@RequestParam(value = "name", defaultValue = "World") String name) {
return greetingService.greet(name);
}
}
GreetingService class as a Spring-managed bean.GreetingController class as a REST controller.GreetingService bean into the GreetingController.@Qualifier to specify which bean to inject.Suppose you have two implementations of a service:
package com.example.dependencyinjectiondemo.service;
import org.springframework.stereotype.Service;
@Service
public class FormalGreetingService implements GreetingService {
@Override
public String greet(String name) {
return "Good day, " + name + ".";
}
}
And another implementation:
package com.example.dependencyinjectiondemo.service;
import org.springframework.stereotype.Service;
@Service
public class InformalGreetingService implements GreetingService {
@Override
public String greet(String name) {
return "Hey, " + name + "!";
}
}
To resolve the ambiguity, use @Qualifier:
package com.example.dependencyinjectiondemo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.example.dependencyinjectiondemo.service.GreetingService;
@RestController
public class GreetingController {
private final GreetingService greetingService;
@Autowired
public GreetingController(@Qualifier("formalGreetingService") GreetingService greetingService) {
this.greetingService = greetingService;
}
@GetMapping("/greet")
public String greet(@RequestParam(value = "name", defaultValue = "World") String name) {
return greetingService.greet(name);
}
}
Testing is crucial when using DI. Spring Boot provides excellent support for testing with its @SpringBootTest annotation.
Create a test class in the com.example.dependencyinjectiondemo package:
package com.example.dependencyinjectiondemo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class GreetingControllerTests {
@Autowired
private TestRestTemplate restTemplate;
@Test
public void testGreetEndpoint() {
String response = this.restTemplate.getForObject("/greet?name=John", String.class);
assertThat(response).isEqualTo("Hello, John!");
}
}
Dependency Injection with Autowiring is a powerful feature in Spring Boot that promotes clean, maintainable code. By following best practices and understanding how autowiring works, you can build robust applications that are easy to test and extend. This tutorial has covered the basics of DI, autowiring, and best practices, along with real-world examples to illustrate these concepts.