Initial commit
This commit is contained in:
@@ -0,0 +1,540 @@
|
||||
# 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.
|
||||
|
||||
```java
|
||||
// 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)
|
||||
|
||||
```java
|
||||
@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.
|
||||
|
||||
```java
|
||||
@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
|
||||
|
||||
```java
|
||||
@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
|
||||
|
||||
```java
|
||||
@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
|
||||
|
||||
```java
|
||||
@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
|
||||
|
||||
```java
|
||||
@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:**
|
||||
```bash
|
||||
export SPRING_PROFILES_ACTIVE=production
|
||||
# or in application.properties:
|
||||
# spring.profiles.active=production
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example 7: Lazy Initialization
|
||||
|
||||
```java
|
||||
@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
|
||||
|
||||
```java
|
||||
// ❌ 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
|
||||
|
||||
```java
|
||||
@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
|
||||
|
||||
```java
|
||||
// ❌ 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.
|
||||
Reference in New Issue
Block a user