Dependency Injection (DI) is a core concept in the Spring Framework that promotes loose coupling and testability by injecting dependencies into a class rather than having the class create them itself. Spring provides several ways to implement Dependency Injection, including constructor injection, setter injection, and field injection.
1. Types of Dependency Injection
Spring supports three main types of dependency injection:
a. Constructor Injection
- Dependencies are injected via the constructor.
- Recommended for mandatory dependencies.
Example: Constructor Injection
public class NotificationService {
private final MessageService messageService;
// Constructor Injection
public NotificationService(MessageService messageService) {
this.messageService = messageService;
}
public void notifyUser(String message) {
messageService.sendMessage(message);
}
}
b. Setter Injection
- Dependencies are injected via setter methods.
- Suitable for optional dependencies.
Example: Setter Injection
public class NotificationService {
private MessageService messageService;
// Setter Injection
public void setMessageService(MessageService messageService) {
this.messageService = messageService;
}
public void notifyUser(String message) {
messageService.sendMessage(message);
}
}
c. Field Injection
- Dependencies are injected directly into fields using annotations.
- Less recommended due to reduced testability.
Example: Field Injection
import org.springframework.beans.factory.annotation.Autowired;
public class NotificationService {
@Autowired
private MessageService messageService;
public void notifyUser(String message) {
messageService.sendMessage(message);
}
}
2. Configuring Dependency Injection
Spring provides two ways to configure dependency injection:
- XML Configuration.
- Annotation-Based Configuration.
a. XML Configuration
Define beans and their dependencies in an XML file.
Example: XML Configuration
<beans>
<bean id="emailService" class="com.example.EmailService" />
<bean id="notificationService" class="com.example.NotificationService">
<constructor-arg ref="emailService" />
</bean>
</beans>
b. Annotation-Based Configuration
Use annotations like @Component
, @Service
, @Repository
, and @Autowired
to configure dependencies.
Example: Annotation-Based Configuration
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class NotificationService {
private final MessageService messageService;
@Autowired
public NotificationService(MessageService messageService) {
this.messageService = messageService;
}
public void notifyUser(String message) {
messageService.sendMessage(message);
}
}
3. Spring Bean Scopes
Spring beans can have different scopes, which determine their lifecycle and visibility.
Scope | Description |
---|---|
Singleton | One instance per Spring IoC container (default scope). |
Prototype | A new instance is created every time the bean is requested. |
Request | One instance per HTTP request (web-aware context only). |
Session | One instance per HTTP session (web-aware context only). |
Application | One instance per ServletContext (web-aware context only). |
WebSocket | One instance per WebSocket session (web-aware context only). |
Example: Configuring Bean Scope
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope("prototype")
public class PrototypeBean {
// Bean logic
}
4. Autowiring
Spring can automatically wire dependencies using the @Autowired
annotation.
Example: Autowiring
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
private final MessageService messageService;
@Autowired
public NotificationService(MessageService messageService) {
this.messageService = messageService;
}
public void notifyUser(String message) {
messageService.sendMessage(message);
}
}
5. Qualifiers
If multiple beans of the same type exist, use the @Qualifier
annotation to specify which bean to inject.
Example: Using Qualifiers
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class NotificationService {
private final MessageService messageService;
@Autowired
public NotificationService(@Qualifier("emailService") MessageService messageService) {
this.messageService = messageService;
}
public void notifyUser(String message) {
messageService.sendMessage(message);
}
}
6. Java-Based Configuration
Use Java classes to configure beans and dependencies.
Example: Java-Based Configuration
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public MessageService emailService() {
return new EmailService();
}
@Bean
public NotificationService notificationService() {
return new NotificationService(emailService());
}
}
7. Best Practices
- Prefer constructor injection for mandatory dependencies.
- Use setter injection for optional dependencies.
- Avoid field injection for better testability.
- Use qualifiers to resolve ambiguity when multiple beans of the same type exist.
- Leverage Java-based configuration for type-safe and refactorable code.
By mastering Spring Dependency Injection, you can build loosely coupled, maintainable, and testable applications!