Before we dive into IOC & DI, let's take a moment to have a clear understanding of what is SPRING π:
- Spring is a popular open-source framework for building enterprise applications in Java. Developed by Pivotal Software (a division of VMware).
- the Spring Framework provides comprehensive infrastructure support, making it easier to develop robust and maintainable Java applications.
- It provide many Key features and components such as : Inversion of Control (IoC), Dependency Injection (DI), Data Access, Transaction Management, Data Access, Model-View-Controller (MVC), Security, Spring Boot ... .
First of all DI is a design pattern and a fundamental concept in software development, particularly in object-oriented programming.
-
It's a technique that promotes loose coupling between different components or classes in a software system, making the code more modular, maintainable, and easier to test.
-
dependency injection refers to the process of providing the required dependencies (objects or services) that a class or component needs to function correctly, rather than letting the class create those dependencies itself.
-
These dependencies are typically passed to the class from an external source, such as a configuration file, a framework, or another class responsible for managing the dependencies
- Definition: Tight coupling refers to a situation where classes or components are highly dependent on each other's concrete implementations. Example: Directly instantiating objects or referencing concrete classes within another class.
- Definition: Loose coupling refers to reducing dependencies between components by relying on abstractions, interfaces, or dependency injection. Example: Using interfaces, abstractions, or DI containers to decouple components.
If we needed to change/replace ClassB with ClassC because ClassC has an optimized version of the calculate() method, we need to recompile ClassA because we don't have a way to change that dependency, it's hardcoded inside of ClassA.
- Loose Coupling βοΈ:
In general it's a design principle that promotes independence between software modules. In the context of Spring and Dependency Injection (DI), it refers to reducing the degree of dependency between components or classes.
- **Flexibility: β»οΈ **
Loose coupling allows for greater flexibility in the codebase. Changes in one module do not heavily impact other modules, making the system more adaptable to modifications.
- Maintainability: π°
Decoupled modules are easier to maintain, as alterations or enhancements to one module are less likely to affect the entire system.
- Testability:
Facilitating isolation of behavior for unit testing.
public class UserService {
private final UserRepository userRepository;
public UserService() {
this.userRepository = new UserRepository(); // Direct instantiation
}
public String getUserFullName(int userId) {
User user = userRepository.getUserById(userId);
return user != null ? user.getFullName() : "User not found";
}
}
Without DI, testing becomes difficult due to direct instantiation of UserRepository
public class UserServiceTestWithoutDI {
@Test
public void testGetUserFullName() {
UserService userService = new UserService();
String fullName = userService.getUserFullName(1);
// ...
}
}
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository; // Dependency injection through constructor
}
public String getUserFullName(int userId) {
User user = userRepository.getUserById(userId);
return user != null ? user.getFullName() : "User not found";
}
}
public class UserServiceTestWithDI {
@Test
public void testGetUserFullName() {
// With DI, we can inject a mock or fake UserRepository for testing
UserRepository mockRepository = Mockito.mock(UserRepository.class);
User user = new User(1, "John Doe");
Mockito.when(mockRepository.getUserById(1)).thenReturn(user);
UserService userService = new UserService(mockRepository);
String fullName = userService.getUserFullName(1);
assertEquals("John Doe", fullName);
}
}
In traditional programming, developers have control over the flow of the application. They create and manage objects, and the application follows the logic written by the developer. IoC is a paradigm shift where control over the application's flow is inverted. Instead of developers controlling the creation and management of objects, this control is handed over to a framework or container.
Constructor injection involves injecting dependencies through the constructor of a class. It is considered a robust and preferred way of injecting dependencies, as it ensures that a class instance is fully initialized when it is created.
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
Setter injection involves injecting dependencies through setter methods. This provides flexibility and allows changing dependencies at runtime.
public class ProductService {
private ProductRepository productRepository;
public void setProductRepository(ProductRepository productRepository) {
this.productRepository = productRepository;
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
}
- Cyclic Dependency Risks π
- Testing Challenges π₯΅
In a Spring application, dependencies are often configured in the Spring configuration files (XML or Java-based) or through annotations. before we dive into example first of all let's understand few annotations that spring provide in order to simplifies the process of configuartion :
- Annotations in Java, and specifically in the context of the Spring framework, serve various purposes to enhance and simplify the development of applications.
-
Component scanning is the process by which Spring identifies classes with stereotype annotations (like @Component) and creates bean definitions for them.
-
Spring uses component scanning to automatically discover and register components in the application context.
- In the context of Spring Framework, stereotype annotations are a group of annotations that define and identify certain types of components. These annotations provide metadata about the roles of annotated classes in the application..
Marks a class as a Spring component, enabling automatic detection and registration as a bean in the Spring context.
@Component
public class MyComponent {
// ... class definition
}
Indicates that a class is a Data Access Object (DAO) and is eligible for exception translation.
@Repository
public class MyRepository {
// ... class definition
}
Marks a class as a service component in the business layer.
@Service
public class MyService {
// ... class definition
}
Marks a class as a controller in the presentation layer.
@Controller
public class MyController {
// ... class definition
}
Used for automatic dependency injection. It can be applied to fields, methods, and constructors.
@Service
public class MyService {
@Autowired
private MyRepository repository;
}
Annotations in Spring serve as metadata that provides additional information to the Spring IoC container or other components. They enhance the configuration, modularity, and manageability of Spring applications.
- In Spring, a bean's scope defines the lifecycle and visibility of that bean within the Spring IoC (Inversion of Control) container. Different scopes are designed to meet various requirements in terms of object instantiation.
- Only one instance of the bean is created, and it is shared across the entire Spring container.
- A new instance of the bean is created every time it is requested.
- This is useful for handling mutable data specific to individual requests.
- A new instance of the bean is created for each HTTP request in a web application.
- suitable for handling request-specific data like form submissions.
- A new instance of the bean is created for each HTTP session in a web application.
- suitable for user-specific data that persists across multiple requests.
Understanding IoC, DI, and leveraging the Spring framework empowers developers to create software that is modular, maintainable, and scalable π―.