Dependency Injection & IoC Container
Dependency Injection (DI) is the core of the Spring Framework. Instead of objects creating their own dependencies, Spring's IoC (Inversion of Control) container creates and injects them, making your code loosely coupled and easily testable.
1 Spring Beans & Stereotypes
Java — Spring Stereotype Annotations
// @Component — generic Spring-managed bean
@Component
public class EmailService {
public void send(String to, String subject) { /* ... */ }
}
// @Service — marks business logic layer
@Service
public class UserService {
// Dependencies are injected, not new-ed up
}
// @Repository — marks data access layer, enables exception translation
@Repository
public interface UserRepository extends JpaRepository<User, Long> {}
// @Controller / @RestController — marks web layer
@RestController
public class UserController {}
2 Constructor Injection (Recommended)
Java — Constructor Injection with Lombok
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor // Lombok generates constructor for all final fields
public class UserService {
private final UserRepository userRepository; // injected
private final EmailService emailService; // injected
private final PasswordEncoder passwordEncoder; // injected
public User createUser(CreateUserRequest req) {
String hash = passwordEncoder.encode(req.getPassword());
User user = new User(req.getName(), req.getEmail(), hash);
User saved = userRepository.save(user);
emailService.send(saved.getEmail(), "Welcome!");
return saved;
}
}
// Why constructor injection?
// - Dependencies are explicit and mandatory
// - Makes classes easily testable (just pass mocks to constructor)
// - Works with final fields (immutable)
// - No circular dependency surprises at runtime
3 @Bean & @Configuration
Java — Manual Bean Definition
@Configuration // This class provides bean definitions
public class AppConfig {
// Register a bean manually (useful for third-party classes)
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
// Profile-specific bean
@Bean
@Profile("dev")
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}
4 Code Challenge
Challenge: Create a
NotificationService interface with two implementations: EmailNotificationService and SmsNotificationService. Use @Primary on email and @Qualifier on SMS. Inject both into a controller and test that the correct one is used.