Files
gh-giuseppe-trisciuoglio-de…/skills/spring-boot/spring-boot-dependency-injection/references/reference.md
2025-11-29 18:28:30 +08:00

15 KiB

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.

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).

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).

@Autowired                        // Required dependency
@Autowired(required = false)      // Optional dependency
@Autowired private UserRepository repository;  // Field injection (avoid)

@Qualifier

Disambiguate when multiple beans of same type exist.

@Autowired
@Qualifier("primaryDB")
private DataSource dataSource;

@Bean
@Qualifier("cache")
public CacheService cacheService() { }

@Primary

Mark bean as preferred when multiple exist.

@Bean
@Primary
public DataSource primaryDataSource() { }

@Bean
public DataSource secondaryDataSource() { }

@Value

Inject properties and SpEL expressions.

@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.

@Bean
@Lazy
public ExpensiveBean expensiveBean() { }

@Autowired
@Lazy
private ExpensiveBean bean;  // Lazy proxy

@Scope

Define bean lifecycle scope.

@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.

@Configuration
public class AppConfig {
    @Bean
    public UserService userService() { }
}

@Bean

Define a bean in configuration class.

@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.

@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.

@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.

@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.

@Bean
@ConditionalOnBean(DataSource.class)
public UserService userService() { }

@Bean
@ConditionalOnMissingBean
public UserService defaultUserService() { }

@ConditionalOnExpression

Create bean based on SpEL expression.

@Bean
@ConditionalOnExpression("'${environment}'.equals('production')")
public SecurityService securityService() { }

Profile-Based Configuration

@Profile

Activate bean only in specific profiles.

@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:

# 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.

@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

@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)

@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)

// ❌ NOT RECOMMENDED
@Service
public class UserService {
    @Autowired
    private UserRepository repository;  // Hidden dependency
    
    @Autowired
    private EmailService emailService;  // Mutable state
}

Circular Dependency Resolution

Problem: Circular Dependencies

// ❌ 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

@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;
    }
}
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

// 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

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

@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

@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

@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

@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)

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)

@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

@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-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