14 KiB
14 KiB
Spring Boot Dependency Injection - Examples
Comprehensive examples demonstrating dependency injection patterns, from basic to advanced scenarios.
Example 1: Constructor Injection (Recommended)
The preferred pattern for mandatory dependencies.
// With Lombok @RequiredArgsConstructor (RECOMMENDED)
@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
private final PasswordEncoder passwordEncoder;
public User registerUser(CreateUserRequest request) {
log.info("Registering user: {}", request.getEmail());
User user = User.builder()
.email(request.getEmail())
.name(request.getName())
.password(passwordEncoder.encode(request.getPassword()))
.build();
User saved = userRepository.save(user);
emailService.sendWelcomeEmail(saved.getEmail());
return saved;
}
}
// Without Lombok (Explicit)
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailService emailService;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository,
EmailService emailService,
PasswordEncoder passwordEncoder) {
this.userRepository = Objects.requireNonNull(userRepository);
this.emailService = Objects.requireNonNull(emailService);
this.passwordEncoder = Objects.requireNonNull(passwordEncoder);
}
public User registerUser(CreateUserRequest request) {
// Implementation
}
}
Test (Easy - No Spring Needed)
@Test
void shouldRegisterUserAndSendEmail() {
// Arrange - Create mocks manually
UserRepository mockRepository = mock(UserRepository.class);
EmailService mockEmailService = mock(EmailService.class);
PasswordEncoder mockEncoder = mock(PasswordEncoder.class);
UserService service = new UserService(mockRepository, mockEmailService, mockEncoder);
User user = User.builder().email("test@example.com").build();
when(mockRepository.save(any())).thenReturn(user);
when(mockEncoder.encode("password")).thenReturn("encoded");
// Act
User result = service.registerUser(new CreateUserRequest("test@example.com", "Test", "password"));
// Assert
assertThat(result.getEmail()).isEqualTo("test@example.com");
verify(mockEmailService).sendWelcomeEmail("test@example.com");
}
Example 2: Setter Injection for Optional Dependencies
Use setter injection ONLY for optional dependencies with sensible defaults.
@Service
public class ReportService {
private final ReportRepository reportRepository;
private EmailService emailService; // Optional
private CacheService cacheService; // Optional
// Constructor for mandatory dependency
public ReportService(ReportRepository reportRepository) {
this.reportRepository = Objects.requireNonNull(reportRepository);
}
// Setters for optional dependencies
@Autowired(required = false)
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
@Autowired(required = false)
public void setCacheService(CacheService cacheService) {
this.cacheService = cacheService;
}
public Report generateReport(ReportRequest request) {
Report report = reportRepository.create(request.getTitle());
// Use optional services if available
if (emailService != null) {
emailService.sendReport(report);
}
if (cacheService != null) {
cacheService.cache(report);
}
return report;
}
}
Example 3: Configuration with Multiple Bean Definitions
@Configuration
public class AppConfig {
// Bean 1: Database
@Bean
public DataSource dataSource(
@Value("${spring.datasource.url}") String url,
@Value("${spring.datasource.username}") String username,
@Value("${spring.datasource.password}") String password) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(20);
return new HikariDataSource(config);
}
// Bean 2: Transaction Manager (depends on DataSource)
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
// Bean 3: Repository (depends on DataSource via JPA)
@Bean
public UserRepository userRepository(UserJpaRepository jpaRepository) {
return new UserRepositoryAdapter(jpaRepository);
}
// Bean 4: Service (depends on Repository)
@Bean
public UserService userService(UserRepository repository) {
return new UserService(repository);
}
}
Example 4: Resolving Ambiguities with @Qualifier
@Configuration
public class DataSourceConfig {
@Bean(name = "primaryDB")
public DataSource primaryDataSource() {
return new HikariDataSource();
}
@Bean(name = "secondaryDB")
public DataSource secondaryDataSource() {
return new HikariDataSource();
}
}
@Service
public class MultiDatabaseService {
private final DataSource primaryDataSource;
private final DataSource secondaryDataSource;
// Using @Qualifier to resolve ambiguity
public MultiDatabaseService(
@Qualifier("primaryDB") DataSource primary,
@Qualifier("secondaryDB") DataSource secondary) {
this.primaryDataSource = primary;
this.secondaryDataSource = secondary;
}
public void performOperation() {
// Use primary for writes
executeUpdate(primaryDataSource);
// Use secondary for reads
executeQuery(secondaryDataSource);
}
}
// Alternative: Using @Primary
@Configuration
public class PrimaryDataSourceConfig {
@Bean
@Primary // This bean is preferred when multiple exist
public DataSource primaryDataSource() {
return new HikariDataSource();
}
@Bean
public DataSource secondaryDataSource() {
return new HikariDataSource();
}
}
Example 5: Conditional Bean Registration
@Configuration
public class OptionalFeatureConfig {
// Only create if feature is enabled
@Bean
@ConditionalOnProperty(name = "feature.notifications.enabled", havingValue = "true")
public NotificationService notificationService() {
return new EmailNotificationService();
}
// Fallback if no other bean exists
@Bean
@ConditionalOnMissingBean(NotificationService.class)
public NotificationService defaultNotificationService() {
return new NoOpNotificationService();
}
// Only create if class is on classpath
@Bean
@ConditionalOnClass(RedisTemplate.class)
public CacheService cacheService() {
return new RedisCacheService();
}
}
@Service
public class OrderService {
private final NotificationService notificationService;
public OrderService(NotificationService notificationService) {
this.notificationService = notificationService; // Works regardless of implementation
}
public void createOrder(Order order) {
// Always works, but behavior depends on enabled features
notificationService.sendConfirmation(order);
}
}
Example 6: Profiles and Environment-Specific Configuration
@Configuration
@Profile("production")
public class ProductionConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://prod-db:5432/production");
config.setMaximumPoolSize(30);
config.setMaxLifetime(1800000); // 30 minutes
return new HikariDataSource(config);
}
@Bean
public SecurityService securityService() {
return new StrictSecurityService();
}
}
@Configuration
@Profile("test")
public class TestConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}
@Bean
public SecurityService securityService() {
return new PermissiveSecurityService();
}
}
@Configuration
@Profile("development")
public class DevelopmentConfig {
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/dev");
return new HikariDataSource(config);
}
@Bean
public SecurityService securityService() {
return new DebugSecurityService();
}
}
Usage:
export SPRING_PROFILES_ACTIVE=production
# or in application.properties:
# spring.profiles.active=production
Example 7: Lazy Initialization
@Configuration
public class ExpensiveResourceConfig {
@Bean
@Lazy // Created only when first accessed
public ExpensiveService expensiveService() {
System.out.println("ExpensiveService initialized (lazy)");
return new ExpensiveService();
}
@Bean
public NormalService normalService(ExpensiveService expensive) {
// ExpensiveService not created yet
return new NormalService(expensive); // Lazy proxy injected here
}
}
@SpringBootTest
class LazyInitializationTest {
@Test
void shouldInitializeExpensiveServiceLazy() {
ApplicationContext context = new AnnotationConfigApplicationContext(ExpensiveResourceConfig.class);
// ExpensiveService not initialized yet
assertThat(context.getBean(NormalService.class)).isNotNull();
// Now ExpensiveService is initialized
ExpensiveService service = context.getBean(ExpensiveService.class);
assertThat(service).isNotNull();
}
}
Example 8: Circular Dependency Resolution with Events
// ❌ BAD - Circular dependency
@Service
public class UserService {
private final OrderService orderService;
public UserService(OrderService orderService) {
this.orderService = orderService; // Circular!
}
}
@Service
public class OrderService {
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService; // Circular!
}
}
// ✅ GOOD - Use events to decouple
public class UserRegisteredEvent extends ApplicationEvent {
private final String userId;
public UserRegisteredEvent(Object source, String userId) {
super(source);
this.userId = userId;
}
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final ApplicationEventPublisher eventPublisher;
public User registerUser(CreateUserRequest request) {
User user = userRepository.save(User.create(request));
eventPublisher.publishEvent(new UserRegisteredEvent(this, user.getId()));
return user;
}
}
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
@EventListener
public void onUserRegistered(UserRegisteredEvent event) {
// Create welcome order when user registers
Order welcomeOrder = Order.createWelcomeOrder(event.getUserId());
orderRepository.save(welcomeOrder);
}
}
Example 9: Component Scanning
@Configuration
@ComponentScan(basePackages = {
"com.example.users",
"com.example.products",
"com.example.orders"
})
public class AppConfig {
}
// Alternative: Exclude packages
@Configuration
@ComponentScan(basePackages = "com.example",
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX,
pattern = "com\\.example\\.internal\\..*"))
public class AppConfig {
}
// Auto-discovered by Spring Boot
@SpringBootApplication // Implies @ComponentScan("package.of.main.class")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Example 10: Testing with Constructor Injection
// ❌ Service with field injection (hard to test)
@Service
public class BadUserService {
@Autowired
private UserRepository userRepository;
public User getUser(Long id) {
return userRepository.findById(id).orElse(null);
}
}
@Test
void testBadService() {
// Must use Spring to test this
UserService service = new BadUserService();
// Can't inject mocks without reflection or Spring
}
// ✅ Service with constructor injection (easy to test)
@Service
@RequiredArgsConstructor
public class GoodUserService {
private final UserRepository userRepository;
public User getUser(Long id) {
return userRepository.findById(id).orElse(null);
}
}
@Test
void testGoodService() {
// Can test directly without Spring
UserRepository mockRepository = mock(UserRepository.class);
UserService service = new GoodUserService(mockRepository);
User mockUser = new User(1L, "Test");
when(mockRepository.findById(1L)).thenReturn(Optional.of(mockUser));
User result = service.getUser(1L);
assertThat(result.getName()).isEqualTo("Test");
}
// Integration test
@SpringBootTest
@ActiveProfiles("test")
class UserServiceIntegrationTest {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
void shouldFetchUserFromDatabase() {
User user = User.create("test@example.com");
userRepository.save(user);
User retrieved = userService.getUser(user.getId());
assertThat(retrieved.getEmail()).isEqualTo("test@example.com");
}
}
These examples cover constructor injection (recommended), setter injection (optional dependencies), configuration, testing patterns, and common best practices for dependency injection in Spring Boot.