Initial commit
This commit is contained in:
150
skills/spring-boot-dependency-injection/SKILL.md
Normal file
150
skills/spring-boot-dependency-injection/SKILL.md
Normal file
@@ -0,0 +1,150 @@
|
||||
---
|
||||
name: spring-boot-dependency-injection
|
||||
description: Dependency injection workflow for Spring Boot projects covering constructor-first patterns, optional collaborator handling, bean selection, and validation practices.
|
||||
allowed-tools: Read, Write, Bash
|
||||
category: backend
|
||||
tags: [spring-boot, dependency-injection, constructor-injection, bean-configuration, autowiring, testing, java]
|
||||
version: 1.1.0
|
||||
context7_library: /spring-projects/spring-framework
|
||||
context7_trust_score: 9.0
|
||||
---
|
||||
|
||||
# Spring Boot Dependency Injection
|
||||
|
||||
This skill captures the dependency injection approach promoted in this repository: constructor-first design, explicit optional collaborators, and deterministic configuration that keeps services testable and framework-agnostic.
|
||||
|
||||
## Overview
|
||||
|
||||
- Prioritize constructor injection to keep dependencies explicit, immutable, and mockable.
|
||||
- Treat optional collaborators through guarded setters or providers while documenting defaults.
|
||||
- Resolve bean ambiguity intentionally through qualifiers, primary beans, and profiles.
|
||||
- Validate wiring with focused unit tests before relying on Spring's TestContext framework.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Implement constructor injection for new `@Service`, `@Component`, or `@Repository` classes.
|
||||
- Replace legacy field injection while modernizing Spring modules.
|
||||
- Configure optional or pluggable collaborators (feature flags, multi-tenant adapters).
|
||||
- Audit bean definitions before adding integration tests or migrating Spring Boot versions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Align project with Java 17+ and Spring Boot 3.5.x (or later) to leverage records and `@ServiceConnection`.
|
||||
- Keep build tooling ready to run `./gradlew test` or `mvn test` for validation.
|
||||
- Load supporting material from `./references/` when deeper patterns or samples are required.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Map Collaborators
|
||||
- Inventory constructors, `@Autowired` members, and configuration classes.
|
||||
- Classify dependencies as mandatory (must exist) or optional (feature-flagged, environment-specific).
|
||||
|
||||
### 2. Apply Constructor Injection
|
||||
- Introduce constructors (or Lombok `@RequiredArgsConstructor`) that accept every mandatory collaborator.
|
||||
- Mark injected fields `final` and protect invariants with `Objects.requireNonNull` if Lombok is not used.
|
||||
- Update `@Configuration` or `@Bean` factories to pass dependencies explicitly; consult `./references/reference.md` for canonical bean wiring.
|
||||
|
||||
### 3. Handle Optional Collaborators
|
||||
- Supply setters annotated with `@Autowired(required = false)` or inject `ObjectProvider<T>` for lazy access.
|
||||
- Provide deterministic defaults (for example, no-op implementations) and document them inside configuration modules.
|
||||
- Follow `./references/examples.md#example-2-setter-injection-for-optional-dependencies` for a full workflow.
|
||||
|
||||
### 4. Resolve Bean Selection
|
||||
- Choose `@Primary` for dominant implementations and `@Qualifier` for niche variants.
|
||||
- Use profiles, conditional annotations, or factory methods to isolate environment-specific wiring.
|
||||
- Reference `./references/reference.md#conditional-bean-registration` for conditional and profile-based samples.
|
||||
|
||||
### 5. Validate Wiring
|
||||
- Write unit tests that instantiate classes manually with mocks to prove Spring-free testability.
|
||||
- Add slice or integration tests (`@WebMvcTest`, `@DataJpaTest`, `@SpringBootTest`) only after constructor contracts are validated.
|
||||
- Reuse patterns in `./references/reference.md#testing-with-dependency-injection` to select the proper test style.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Constructor Injection
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserService {
|
||||
private final UserRepository userRepository;
|
||||
private final EmailService emailService;
|
||||
|
||||
public User register(UserRegistrationRequest request) {
|
||||
User user = User.create(request.email(), request.name());
|
||||
userRepository.save(user);
|
||||
emailService.sendWelcome(user);
|
||||
return user;
|
||||
}
|
||||
}
|
||||
```
|
||||
- Instantiate directly in tests: `new UserService(mockRepo, mockEmailService);` with no Spring context required.
|
||||
|
||||
### Intermediate: Optional Dependency with Guarded Setter
|
||||
```java
|
||||
@Service
|
||||
public class ReportService {
|
||||
private final ReportRepository reportRepository;
|
||||
private CacheService cacheService = CacheService.noOp();
|
||||
|
||||
public ReportService(ReportRepository reportRepository) {
|
||||
this.reportRepository = reportRepository;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setCacheService(CacheService cacheService) {
|
||||
this.cacheService = cacheService;
|
||||
}
|
||||
}
|
||||
```
|
||||
- Provide fallbacks such as `CacheService.noOp()` to ensure deterministic behavior when the optional bean is absent.
|
||||
|
||||
### Advanced: Conditional Configuration Across Modules
|
||||
```java
|
||||
@Configuration
|
||||
@Import(DatabaseConfig.class)
|
||||
public class MessagingConfig {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "feature.notifications.enabled", havingValue = "true")
|
||||
public NotificationService emailNotificationService(JavaMailSender sender) {
|
||||
return new EmailNotificationService(sender);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(NotificationService.class)
|
||||
public NotificationService noopNotificationService() {
|
||||
return NotificationService.noOp();
|
||||
}
|
||||
}
|
||||
```
|
||||
- Combine `@Import`, profiles, and conditional annotations to orchestrate cross-cutting modules.
|
||||
|
||||
Additional worked examples (including tests and configuration wiring) are available in `./references/examples.md`.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Prefer constructor injection for mandatory dependencies; allow Spring 4.3+ to infer `@Autowired` on single constructors.
|
||||
- Encapsulate optional behavior inside dedicated adapters or providers instead of accepting `null` pointers.
|
||||
- Keep service constructors lightweight; extract orchestrators when dependency counts exceed four.
|
||||
- Favor domain interfaces in the domain layer and defer framework imports to infrastructure adapters.
|
||||
- Document bean names and qualifiers in shared constants to avoid typo-driven mismatches.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Avoid field injection and service locator patterns because they obscure dependencies and impede unit testing.
|
||||
- Prevent circular dependencies by publishing domain events or extracting shared abstractions.
|
||||
- Limit `@Lazy` usage to performance-sensitive paths and record the deferred initialization risk.
|
||||
- Do not add profile-specific beans without matching integration tests that activate the profile.
|
||||
- Ensure each optional collaborator has a deterministic default or feature-flag handling path.
|
||||
|
||||
## Reference Materials
|
||||
|
||||
- [extended documentation covering annotations, bean scopes, testing, and anti-pattern mitigations](references/reference.md)
|
||||
- [progressive examples from constructor injection basics to multi-module configurations](references/examples.md)
|
||||
- [curated excerpts from the official Spring Framework documentation (constructor vs setter guidance, conditional wiring)](references/spring-official-dependency-injection.md)
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `spring-boot-crud-patterns` – service-layer orchestration patterns that rely on constructor injection.
|
||||
- `spring-boot-rest-api-standards` – controller-layer practices that assume explicit dependency wiring.
|
||||
- `unit-test-service-layer` – Mockito-based testing patterns for constructor-injected services.
|
||||
540
skills/spring-boot-dependency-injection/references/examples.md
Normal file
540
skills/spring-boot-dependency-injection/references/examples.md
Normal file
@@ -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.
|
||||
640
skills/spring-boot-dependency-injection/references/reference.md
Normal file
640
skills/spring-boot-dependency-injection/references/reference.md
Normal file
@@ -0,0 +1,640 @@
|
||||
# Spring Boot Dependency Injection - References
|
||||
|
||||
Complete API reference for dependency injection in Spring Boot applications.
|
||||
|
||||
## Core Interfaces and Classes
|
||||
|
||||
### ApplicationContext
|
||||
Root interface for Spring IoC container.
|
||||
|
||||
```java
|
||||
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory,
|
||||
HierarchicalBeanFactory, MessageSource,
|
||||
ApplicationEventPublisher, ResourcePatternResolver {
|
||||
|
||||
// Get a bean by type
|
||||
<T> T getBean(Class<T> requiredType);
|
||||
|
||||
// Get a bean by name and type
|
||||
<T> T getBean(String name, Class<T> requiredType);
|
||||
|
||||
// Get all beans of a type
|
||||
<T> Map<String, T> getBeansOfType(Class<T> type);
|
||||
|
||||
// Get all bean names
|
||||
String[] getBeanDefinitionNames();
|
||||
}
|
||||
```
|
||||
|
||||
### BeanFactory
|
||||
Lower-level interface for accessing beans (used internally).
|
||||
|
||||
```java
|
||||
public interface BeanFactory {
|
||||
Object getBean(String name);
|
||||
<T> T getBean(String name, Class<T> requiredType);
|
||||
<T> T getBean(Class<T> requiredType);
|
||||
Object getBean(String name, Object... args);
|
||||
}
|
||||
```
|
||||
|
||||
## Dependency Injection Annotations
|
||||
|
||||
### @Autowired
|
||||
Auto-wire dependencies (property, constructor, or method injection).
|
||||
|
||||
```java
|
||||
@Autowired // Required dependency
|
||||
@Autowired(required = false) // Optional dependency
|
||||
@Autowired private UserRepository repository; // Field injection (avoid)
|
||||
```
|
||||
|
||||
### @Qualifier
|
||||
Disambiguate when multiple beans of same type exist.
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
@Qualifier("primaryDB")
|
||||
private DataSource dataSource;
|
||||
|
||||
@Bean
|
||||
@Qualifier("cache")
|
||||
public CacheService cacheService() { }
|
||||
```
|
||||
|
||||
### @Primary
|
||||
Mark bean as preferred when multiple exist.
|
||||
|
||||
```java
|
||||
@Bean
|
||||
@Primary
|
||||
public DataSource primaryDataSource() { }
|
||||
|
||||
@Bean
|
||||
public DataSource secondaryDataSource() { }
|
||||
```
|
||||
|
||||
### @Value
|
||||
Inject properties and SpEL expressions.
|
||||
|
||||
```java
|
||||
@Value("${app.name}") // Property injection
|
||||
@Value("${app.port:8080}") // With default value
|
||||
@Value("#{T(java.lang.Math).PI}") // SpEL expression
|
||||
@Value("#{'${app.servers}'.split(',')}") // Collection
|
||||
private String value;
|
||||
```
|
||||
|
||||
### @Lazy
|
||||
Delay bean initialization until first access.
|
||||
|
||||
```java
|
||||
@Bean
|
||||
@Lazy
|
||||
public ExpensiveBean expensiveBean() { }
|
||||
|
||||
@Autowired
|
||||
@Lazy
|
||||
private ExpensiveBean bean; // Lazy proxy
|
||||
```
|
||||
|
||||
### @Scope
|
||||
Define bean lifecycle scope.
|
||||
|
||||
```java
|
||||
@Scope("singleton") // One per container (default)
|
||||
@Scope("prototype") // New instance each time
|
||||
@Scope("request") // One per HTTP request
|
||||
@Scope("session") // One per HTTP session
|
||||
@Scope("application") // One per ServletContext
|
||||
@Scope("websocket") // One per WebSocket session
|
||||
```
|
||||
|
||||
### @Configuration
|
||||
Mark class as providing bean definitions.
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@Bean
|
||||
public UserService userService() { }
|
||||
}
|
||||
```
|
||||
|
||||
### @Bean
|
||||
Define a bean in configuration class.
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public UserService userService(UserRepository repository) {
|
||||
return new UserService(repository);
|
||||
}
|
||||
|
||||
@Bean(name = "customName")
|
||||
public UserService userService() { }
|
||||
|
||||
@Bean(initMethod = "init", destroyMethod = "cleanup")
|
||||
public UserService userService() { }
|
||||
```
|
||||
|
||||
### @Component / @Service / @Repository / @Controller
|
||||
Stereotype annotations for component scanning.
|
||||
|
||||
```java
|
||||
@Component // Generic Spring component
|
||||
@Service // Business logic layer
|
||||
@Repository // Data access layer
|
||||
@Controller // Web layer (MVC)
|
||||
@RestController // Web layer (REST)
|
||||
public class UserService { }
|
||||
```
|
||||
|
||||
## Conditional Bean Registration
|
||||
|
||||
### @ConditionalOnProperty
|
||||
Create bean only if property exists.
|
||||
|
||||
```java
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
name = "feature.notifications.enabled",
|
||||
havingValue = "true"
|
||||
)
|
||||
public NotificationService notificationService() { }
|
||||
|
||||
// OR if property matches any value
|
||||
@ConditionalOnProperty(name = "feature.enabled")
|
||||
public NotificationService notificationService() { }
|
||||
```
|
||||
|
||||
### @ConditionalOnClass / @ConditionalOnMissingClass
|
||||
Create bean based on classpath availability.
|
||||
|
||||
```java
|
||||
@Bean
|
||||
@ConditionalOnClass(RedisTemplate.class)
|
||||
public CacheService cacheService() { }
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingClass("org.springframework.data.redis.core.RedisTemplate")
|
||||
public LocalCacheService fallbackCacheService() { }
|
||||
```
|
||||
|
||||
### @ConditionalOnBean / @ConditionalOnMissingBean
|
||||
Create bean based on other beans.
|
||||
|
||||
```java
|
||||
@Bean
|
||||
@ConditionalOnBean(DataSource.class)
|
||||
public UserService userService() { }
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public UserService defaultUserService() { }
|
||||
```
|
||||
|
||||
### @ConditionalOnExpression
|
||||
Create bean based on SpEL expression.
|
||||
|
||||
```java
|
||||
@Bean
|
||||
@ConditionalOnExpression("'${environment}'.equals('production')")
|
||||
public SecurityService securityService() { }
|
||||
```
|
||||
|
||||
## Profile-Based Configuration
|
||||
|
||||
### @Profile
|
||||
Activate bean only in specific profiles.
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@Profile("production")
|
||||
public class ProductionConfig { }
|
||||
|
||||
@Bean
|
||||
@Profile({"dev", "test"})
|
||||
public TestDataLoader testDataLoader() { }
|
||||
|
||||
@Bean
|
||||
@Profile("!production") // All profiles except production
|
||||
public DebugService debugService() { }
|
||||
```
|
||||
|
||||
**Activate profiles:**
|
||||
```properties
|
||||
# application.properties
|
||||
spring.profiles.active=production
|
||||
|
||||
# application-production.properties
|
||||
# Profile-specific property file
|
||||
spring.datasource.url=jdbc:postgresql://prod-db:5432/prod
|
||||
```
|
||||
|
||||
## Component Scanning
|
||||
|
||||
### @ComponentScan
|
||||
Configure component scanning.
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
@ComponentScan(basePackages = {"com.example.users", "com.example.products"})
|
||||
public class AppConfig { }
|
||||
|
||||
@Configuration
|
||||
@ComponentScan(
|
||||
basePackages = "com.example",
|
||||
excludeFilters = @ComponentScan.Filter(
|
||||
type = FilterType.REGEX,
|
||||
pattern = "com\\.example\\.internal\\..*"
|
||||
)
|
||||
)
|
||||
public class AppConfig { }
|
||||
```
|
||||
|
||||
### Filter Types
|
||||
- `FilterType.ANNOTATION` - By annotation
|
||||
- `FilterType.ASSIGNABLE_TYPE` - By class type
|
||||
- `FilterType.ASPECTJ` - By AspectJ pattern
|
||||
- `FilterType.REGEX` - By regex pattern
|
||||
- `FilterType.CUSTOM` - Custom filter
|
||||
|
||||
## Injection Points
|
||||
|
||||
### Constructor Injection (Recommended)
|
||||
|
||||
```java
|
||||
@Service
|
||||
@RequiredArgsConstructor // Lombok generates constructor
|
||||
public class UserService {
|
||||
private final UserRepository repository; // Final field
|
||||
private final EmailService emailService;
|
||||
}
|
||||
|
||||
// Explicit
|
||||
@Service
|
||||
public class UserService {
|
||||
private final UserRepository repository;
|
||||
|
||||
public UserService(UserRepository repository) {
|
||||
this.repository = Objects.requireNonNull(repository);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Setter Injection (Optional Dependencies Only)
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class UserService {
|
||||
private final UserRepository repository;
|
||||
private EmailService emailService; // Optional
|
||||
|
||||
public UserService(UserRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setEmailService(EmailService emailService) {
|
||||
this.emailService = emailService;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Field Injection (❌ Avoid)
|
||||
|
||||
```java
|
||||
// ❌ NOT RECOMMENDED
|
||||
@Service
|
||||
public class UserService {
|
||||
@Autowired
|
||||
private UserRepository repository; // Hidden dependency
|
||||
|
||||
@Autowired
|
||||
private EmailService emailService; // Mutable state
|
||||
}
|
||||
```
|
||||
|
||||
## Circular Dependency Resolution
|
||||
|
||||
### Problem: Circular Dependencies
|
||||
|
||||
```java
|
||||
// ❌ WILL FAIL
|
||||
@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!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Solution 1: Setter Injection
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class UserService {
|
||||
private final UserRepository userRepository;
|
||||
private OrderService orderService; // Optional
|
||||
|
||||
public UserService(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
public void setOrderService(OrderService orderService) {
|
||||
this.orderService = orderService;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Solution 2: Event-Driven (Recommended)
|
||||
|
||||
```java
|
||||
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) {
|
||||
orderRepository.createWelcomeOrder(event.getUserId());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Solution 3: Refactor to Separate Concerns
|
||||
|
||||
```java
|
||||
// Shared service without circular dependency
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserOrderService {
|
||||
private final UserRepository userRepository;
|
||||
private final OrderRepository orderRepository;
|
||||
}
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UserService {
|
||||
private final UserOrderService userOrderService;
|
||||
}
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OrderService {
|
||||
private final UserOrderService userOrderService;
|
||||
}
|
||||
```
|
||||
|
||||
## ObjectProvider for Flexibility
|
||||
|
||||
### ObjectProvider Interface
|
||||
|
||||
```java
|
||||
public interface ObjectProvider<T> extends ObjectFactory<T>, Iterable<T> {
|
||||
T getObject();
|
||||
T getObject(Object... args);
|
||||
T getIfAvailable();
|
||||
T getIfAvailable(Supplier<T> defaultSupplier);
|
||||
void ifAvailable(Consumer<T> consumer);
|
||||
void ifAvailableOrElse(Consumer<T> consumer, Runnable emptyRunnable);
|
||||
<X> ObjectProvider<X> map(Function<? super T, ? extends X> mapper);
|
||||
<X> ObjectProvider<X> flatMap(Function<? super T, ObjectProvider<X>> mapper);
|
||||
Optional<T> getIfUnique();
|
||||
Optional<T> getIfUnique(Supplier<T> defaultSupplier);
|
||||
}
|
||||
```
|
||||
|
||||
### Usage Example
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class FlexibleService {
|
||||
private final ObjectProvider<CacheService> cacheProvider;
|
||||
|
||||
public FlexibleService(ObjectProvider<CacheService> cacheProvider) {
|
||||
this.cacheProvider = cacheProvider;
|
||||
}
|
||||
|
||||
public void process() {
|
||||
// Safely handle optional bean
|
||||
cacheProvider.ifAvailable(cache -> cache.invalidate());
|
||||
|
||||
// Get with fallback
|
||||
CacheService cache = cacheProvider.getIfAvailable(() -> new NoOpCache());
|
||||
|
||||
// Iterate if multiple beans exist
|
||||
cacheProvider.forEach(cache -> cache.initialize());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Bean Lifecycle Hooks
|
||||
|
||||
### InitializingBean / DisposableBean
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class ResourceManager implements InitializingBean, DisposableBean {
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
// Called after constructor and property injection
|
||||
System.out.println("Bean initialized");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
// Called when context shutdown
|
||||
System.out.println("Bean destroyed");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @PostConstruct / @PreDestroy
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class ResourceManager {
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// Called after constructor and injection
|
||||
System.out.println("Bean initialized");
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void cleanup() {
|
||||
// Called before bean destroyed
|
||||
System.out.println("Bean destroyed");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @Bean with initMethod and destroyMethod
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
|
||||
@Bean(initMethod = "init", destroyMethod = "cleanup")
|
||||
public ResourceManager resourceManager() {
|
||||
return new ResourceManager();
|
||||
}
|
||||
}
|
||||
|
||||
public class ResourceManager {
|
||||
public void init() {
|
||||
System.out.println("Initialized");
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
System.out.println("Cleaned up");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
### Unit Test (No Spring)
|
||||
|
||||
```java
|
||||
class UserServiceTest {
|
||||
private UserRepository mockRepository;
|
||||
private UserService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
mockRepository = mock(UserRepository.class);
|
||||
service = new UserService(mockRepository); // Manual injection
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFetchUser() {
|
||||
User user = new User(1L, "Test");
|
||||
when(mockRepository.findById(1L)).thenReturn(Optional.of(user));
|
||||
|
||||
User result = service.getUser(1L);
|
||||
assertThat(result).isEqualTo(user);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Test (With Spring)
|
||||
|
||||
```java
|
||||
@SpringBootTest
|
||||
@ActiveProfiles("test")
|
||||
class UserServiceIntegrationTest {
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
userRepository.deleteAll();
|
||||
}
|
||||
|
||||
@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");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Slice Test
|
||||
|
||||
```java
|
||||
@WebMvcTest(UserController.class)
|
||||
class UserControllerTest {
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@MockBean // Mock the service
|
||||
private UserService userService;
|
||||
|
||||
@Test
|
||||
void shouldReturnUser() throws Exception {
|
||||
User user = new User(1L, "Test");
|
||||
when(userService.getUser(1L)).thenReturn(user);
|
||||
|
||||
mockMvc.perform(get("/users/1"))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
| Practice | Recommendation | Why |
|
||||
|----------|---|---|
|
||||
| Constructor injection | ✅ Mandatory | Explicit, immutable, testable |
|
||||
| Setter injection | ⚠️ Optional deps | Clear optionality |
|
||||
| Field injection | ❌ Never | Hidden, untestable |
|
||||
| @Autowired on constructor | ✅ Implicit (4.3+) | Clear intent |
|
||||
| Lombok @RequiredArgsConstructor | ✅ Recommended | Reduces boilerplate |
|
||||
| Circular dependencies | ❌ Avoid | Use events instead |
|
||||
| Too many dependencies | ❌ Avoid | SRP violation |
|
||||
| @Lazy for expensive beans | ✅ Appropriate | Faster startup |
|
||||
| Profiles for environments | ✅ Recommended | Environment-specific config |
|
||||
| @Value for properties | ✅ Recommended | Type-safe injection |
|
||||
|
||||
## External Resources
|
||||
|
||||
### Official Documentation
|
||||
- [Spring IoC Container](https://docs.spring.io/spring-framework/reference/core/beans.html)
|
||||
- [Spring Boot Auto-Configuration](https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.auto-configuration)
|
||||
- [Conditional Bean Registration](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration.condition-annotations)
|
||||
|
||||
### Related Skills
|
||||
- **spring-boot-crud-patterns/SKILL.md** - DI in CRUD applications
|
||||
- **spring-boot-test-patterns/SKILL.md** - Testing with DI
|
||||
- **spring-boot-rest-api-standards/SKILL.md** - REST layer with DI
|
||||
|
||||
### Books
|
||||
- "Spring in Action" (latest edition)
|
||||
- "Spring Microservices in Action"
|
||||
|
||||
### Articles
|
||||
- [Baeldung Spring Dependency Injection](https://www.baeldung.com/spring-dependency-injection)
|
||||
- [Martin Fowler IoC](https://www.martinfowler.com/articles/injection.html)
|
||||
@@ -0,0 +1,59 @@
|
||||
# Spring Framework Official Guidance: Dependency Injection (Clean Excerpt)
|
||||
|
||||
Source: https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html (retrieved via `u2m -v` on current date).
|
||||
|
||||
## Key Highlights
|
||||
- Emphasize constructor-based dependency injection to make collaborators explicit and enable immutable design.
|
||||
- Use setter injection only for optional dependencies or when a dependency can change after initialization.
|
||||
- Field injection is supported but discouraged because it hides dependencies and complicates testing.
|
||||
- The IoC container resolves constructor arguments by type, name, and order; prefer unique types or qualify arguments with `@Qualifier` or XML attributes when ambiguity exists.
|
||||
- Static factory methods behave like constructors for dependency injection and can receive collaborators through arguments.
|
||||
|
||||
## Constructor-Based DI
|
||||
```java
|
||||
public class SimpleMovieLister {
|
||||
private final MovieFinder movieFinder;
|
||||
|
||||
public SimpleMovieLister(MovieFinder movieFinder) {
|
||||
this.movieFinder = movieFinder;
|
||||
}
|
||||
}
|
||||
```
|
||||
- The container selects the matching constructor and provides dependencies by type.
|
||||
- When argument types are ambiguous, specify indexes (`@ConstructorProperties`, XML `index` attribute) or qualifiers.
|
||||
|
||||
## Setter-Based DI
|
||||
```java
|
||||
public class SimpleMovieLister {
|
||||
private MovieFinder movieFinder;
|
||||
|
||||
@Autowired
|
||||
public void setMovieFinder(MovieFinder movieFinder) {
|
||||
this.movieFinder = movieFinder;
|
||||
}
|
||||
}
|
||||
```
|
||||
- Invoke only when a collaborator is optional or changeable.
|
||||
- Use `@Autowired(required = false)` or `ObjectProvider<T>` to guard optional collaborators.
|
||||
|
||||
## Reference Snippets
|
||||
```xml
|
||||
<bean id="exampleBean" class="examples.ExampleBean">
|
||||
<constructor-arg ref="anotherExampleBean"/>
|
||||
<constructor-arg ref="yetAnotherBean"/>
|
||||
<constructor-arg value="1"/>
|
||||
</bean>
|
||||
|
||||
<bean id="exampleBean" class="examples.ExampleBean">
|
||||
<property name="beanOne" ref="anotherExampleBean"/>
|
||||
<property name="beanTwo" ref="yetAnotherBean"/>
|
||||
</bean>
|
||||
```
|
||||
- Spring treats constructor-arg entries as positional parameters unless `index` or `type` is provided.
|
||||
- Setter injection uses `<property>` elements mapped by name.
|
||||
|
||||
## Additional Notes
|
||||
- Combine configuration classes with `@Import` to wire dependencies declared in different modules.
|
||||
- Lazy initialization (`@Lazy`) delays bean creation but defers error detection; prefer eager initialization unless startup time is critical.
|
||||
- Profiles (`@Profile`) activate different wiring scenarios per environment (for example, `@Profile("test")`).
|
||||
- Testing support allows constructor injection in production code while wiring mocks manually (no container required) or relying on the TestContext framework for integration tests.
|
||||
Reference in New Issue
Block a user