Spring Dependency Injection
IoC and Dependency Injection
Inversion of Control (IoC) is a design principle where the control of object creation and lifecycle is transferred from the application code to a container (the Spring IoC container). Dependency Injection (DI) is the mechanism Spring uses to implement IoC — it injects dependencies into objects rather than having objects create their own dependencies.
Spring supports three types of dependency injection:
- Constructor Injection — Dependencies injected via constructor (recommended)
- Setter Injection — Dependencies injected via setter methods
- Field Injection — Dependencies injected directly into fields via
@Autowired
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class OrderService {
private final UserRepository userRepository;
private final EmailService emailService;
private final PaymentService paymentService;
// Constructor injection - RECOMMENDED
// @Autowired is optional when there's only one constructor (Spring 4.3+)
@Autowired
public OrderService(UserRepository userRepository,
EmailService emailService,
PaymentService paymentService) {
this.userRepository = userRepository;
this.emailService = emailService;
this.paymentService = paymentService;
}
// Benefits:
// 1. Dependencies are immutable (final fields)
// 2. Mandatory dependencies are explicit
// 3. Easy to test (just pass mocks to constructor)
// 4. Detects circular dependencies at startup
}
@Service
public class NotificationService {
private EmailService emailService;
private SmsService smsService;
// Setter injection - for optional dependencies
@Autowired
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
@Autowired(required = false) // Optional dependency
public void setSmsService(SmsService smsService) {
this.smsService = smsService;
}
public void notify(String message) {
emailService.send(message);
if (smsService != null) {
smsService.send(message);
}
}
}
@Service
public class ProductService {
// Field injection - NOT recommended for production
// (harder to test, hides dependencies, can't use final)
@Autowired
private ProductRepository productRepository;
@Autowired
private CacheService cacheService;
// Works but considered bad practice
// Use constructor injection instead
}
@Qualifier, @Primary, and @Value
// When multiple beans of the same type exist, use @Qualifier or @Primary
// Two implementations of NotificationService
@Service("emailNotification")
public class EmailNotificationService implements NotificationService { ... }
@Service("smsNotification")
@Primary // This one is injected by default when no @Qualifier is specified
public class SmsNotificationService implements NotificationService { ... }
// Injecting with @Qualifier
@Service
public class AlertService {
private final NotificationService emailService;
private final NotificationService smsService;
@Autowired
public AlertService(
@Qualifier("emailNotification") NotificationService emailService,
@Qualifier("smsNotification") NotificationService smsService) {
this.emailService = emailService;
this.smsService = smsService;
}
}
// @Value: inject values from application.properties
@Service
public class AppConfigService {
@Value("${app.name}")
private String appName;
@Value("${app.version:1.0}") // Default value if property not found
private String version;
@Value("${server.port}")
private int port;
@Value("${app.features:feature1,feature2}") // Comma-separated list
private List<String> features;
}
@Configuration and @Bean
import org.springframework.context.annotation.*;
import org.springframework.beans.factory.annotation.Value;
@Configuration // Marks this as a Spring configuration class
public class AppConfig {
@Value("${app.name}")
private String appName;
// @Bean: manually define a bean (useful for third-party classes)
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean(name = "customObjectMapper")
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
// Bean with dependency on another bean
@Bean
public EmailService emailService(MailSender mailSender) {
return new EmailServiceImpl(mailSender, appName);
}
}
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.