Understanding Dependency Injection in Spring Boot

Master Dependency Injection in Spring Boot with this beginner-friendly guide

In modern software development, the goal is to create applications that are flexible, maintainable, and testable. One of the key patterns used to achieve this is Dependency Injection (DI). If you’re building applications with Spring Boot, understanding DI is essential. In this blog post, we’ll explore what Dependency Injection is, why it’s important, and how it works in Spring Boot, all from a beginner’s perspective. Plus, we’ll show how DI simplifies testing and makes your code more modular and maintainable.

What is Dependency Injection?

Dependency Injection is a design pattern where an object (often referred to as a “dependency”) is passed (or injected) into another object, rather than the dependent object creating the dependency itself. In simpler terms, DI allows objects to be loosely coupled by having their dependencies provided by an external framework or container, rather than creating them manually.

In Spring Boot, the Spring Framework takes care of injecting dependencies, making the process smooth and automatic.

Example Without Dependency Injection

Without DI, you might create and manage dependencies manually, like this:

public class UserService {

    private UserRepository userRepository = new UserRepository();

    public void performAction() {
        // Use the userRepository instance
    }
}

Here, UserService is tightly coupled to UserRepository, meaning if you want to change UserRepository, you would need to modify UserService, breaking flexibility.

Example With Dependency Injection

With DI, you externalize the creation of UserRepository, letting the Spring Framework inject it for you:

@Service
public class UserService {

    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void performAction() {
        // Use the injected userRepository instance
    }
}

Now, UserService is loosely coupled and doesn’t need to manage the lifecycle of UserRepository. The Spring container will handle that for you!

Why Use Dependency Injection?

1. Loose Coupling

DI helps create loosely coupled systems, where objects depend on abstractions rather than concrete implementations. This means your code is more flexible and easier to extend or modify without affecting other parts of the system.

2. Easier Unit Testing

By injecting dependencies, you can easily replace real objects with mock or stub objects during testing. This makes unit testing easier, as you can test components in isolation.

For example, using DI, you can pass a mock version of UserRepository during testing:

@Test
void testUserService() {

    UserRepository mockRepository = mock(UserRepository.class);
    UserService userService = new UserService(mockRepository);

    // Write assertions and test userService
}

3. Maintainability and Scalability

As applications grow, maintaining tightly coupled code becomes challenging. DI makes it easy to modify, extend, or replace components without breaking the rest of the application.

4. Inversion of Control

DI is a form of Inversion of Control (IoC), where the control of creating and managing dependencies is moved from the object itself to an external container (the Spring Framework). This makes managing object lifecycles much easier, especially in large applications.

Spring Boot and Dependency Injection

In Spring Boot, Dependency Injection is managed through Spring Beans. A Bean is an object that the Spring container manages, and Spring handles the lifecycle and injections of these beans. The framework supports three types of injection:

  1. Constructor Injection (preferred and shown in our example)
  2. Setter Injection
  3. Field Injection (discouraged, as it reduces testability)

Scopes of Spring Beans

Spring Boot provides several bean scopes, which define the lifecycle of a bean:

  • Singleton (default): A single instance of the bean is created and shared across the entire application.
  • Prototype: A new instance of the bean is created every time it is requested.
  • Request/Session: Specific to web applications, where beans are created for every HTTP request or session.

For beginners, it’s recommended to stick with the default singleton scope unless you have a specific need for other scopes.

Example: Using Dependency Injection in Spring Boot

Let’s walk through a simple example using Spring Boot.

Step 1: Defining a Service and Repository

@Repository
public class UserRepository {

    public String findUser() {
        return "John Doe";
    }
}

@Service
public class UserService {

    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public String getUser() {
        return userRepository.findUser();
    }
}

In this example:

  • UserRepository is a bean annotated with @Repository, managed by the Spring container.
  • UserService is a bean annotated with @Service, which gets the UserRepository bean injected automatically through the constructor.

💡 Heads up! It’s not required to annotate the constructor with @Autowired unless you have more than one constructor. This way, by annotating one with @Autowired , Spring will know which one to use.

Step 2: Autowiring Beans in a Controller

@RestController
public class UserController {

    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/user")
    public String getUser() {
        return userService.getUser();
    }
}

In this REST controller:

  • UserService is injected into UserController.
  • When a request is made to /user, it returns a user fetched from the service.

Step 3: Running the Application

Once your application is set up, Spring Boot will automatically inject the UserRepository into the UserService, and the UserService into the UserController.

Conclusion

Dependency Injection is a foundational concept in Spring Boot that allows you to build flexible, testable, and maintainable applications. By understanding DI, you’ll be better equipped to manage your dependencies, decouple your components, and make your code more scalable. With DI, unit testing becomes more straightforward, allowing you to mock dependencies and focus on testing your logic in isolation.

Whether you’re building a small app or scaling to enterprise-level applications, learning Dependency Injection will improve the way you design and structure your Spring Boot projects.