Skip to content

Spring IoC

What is IoC?

Inversion of Control is a design principle where the control of object creation and lifecycle is transferred from the application code to a container/framework.

Traditional vs IoC


Dependency Injection Types

@Service
public class OrderService {

    private final PaymentService paymentService;
    private final InventoryService inventoryService;

    // Constructor injection - immutable, testable
    @Autowired  // Optional in Spring 4.3+ if single constructor
    public OrderService(PaymentService paymentService,
                       InventoryService inventoryService) {
        this.paymentService = paymentService;
        this.inventoryService = inventoryService;
    }

    public void processOrder(Order order) {
        inventoryService.reserve(order.getItems());
        paymentService.charge(order.getPayment());
    }
}

Advantages: - Fields can be final (immutability) - Clear dependencies at construction - Easy to test (pass mocks via constructor) - Fails fast if dependency missing

2. Setter Injection

@Service
public class NotificationService {

    private EmailService emailService;
    private SmsService smsService;

    @Autowired
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }

    @Autowired(required = false)  // Optional dependency
    public void setSmsService(SmsService smsService) {
        this.smsService = smsService;
    }
}

Use when: Dependencies are optional or need to be changed at runtime.

3. Field Injection (Avoid)

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;  // Hard to test!

    @Autowired
    private PasswordEncoder passwordEncoder;
}

Problems: - Cannot make fields final - Hard to test without Spring context - Hides dependencies - Can lead to circular dependencies


Spring Container

ApplicationContext vs BeanFactory

Container Hierarchy

Creating Context

// Java-based configuration
@Configuration
@ComponentScan("com.example")
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:postgresql://localhost/mydb");
        ds.setUsername("user");
        ds.setPassword("pass");
        return ds;
    }
}

// Bootstrap
ApplicationContext context =
    new AnnotationConfigApplicationContext(AppConfig.class);
OrderService orderService = context.getBean(OrderService.class);

Bean Definition

Stereotype Annotations

@Component          // Generic component
@Service            // Business logic layer
@Repository         // Data access layer (adds exception translation)
@Controller         // Web MVC controller
@RestController     // @Controller + @ResponseBody
@Configuration      // Configuration class (contains @Bean methods)

@Bean vs @Component

// @Component - Class-level, auto-detected via component scanning
@Component
public class MyService {
    // Spring creates and manages this bean
}

// @Bean - Method-level in @Configuration class
@Configuration
public class AppConfig {

    @Bean
    public ThirdPartyService thirdPartyService() {
        // Use for classes you don't own
        return new ThirdPartyService(apiKey());
    }

    @Bean
    public String apiKey() {
        return System.getenv("API_KEY");
    }
}

Bean Scopes

Bean Scopes

Scope Examples

@Component
@Scope("singleton")  // Default - can omit
public class CacheService {
    // Single instance shared across application
}

@Component
@Scope("prototype")
public class ShoppingCart {
    // New instance each time injected or requested
}

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST,
       proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
    // One per HTTP request
}

Prototype in Singleton Problem

@Service
public class OrderProcessor {

    // WRONG: Prototype injected once into singleton
    @Autowired
    private ShoppingCart cart;  // Same instance always!

    // SOLUTION 1: ObjectFactory
    @Autowired
    private ObjectFactory<ShoppingCart> cartFactory;

    public void process() {
        ShoppingCart cart = cartFactory.getObject();  // New each time
    }

    // SOLUTION 2: Provider (JSR-330)
    @Autowired
    private Provider<ShoppingCart> cartProvider;

    public void process() {
        ShoppingCart cart = cartProvider.get();  // New each time
    }

    // SOLUTION 3: Scoped Proxy
    @Autowired
    @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
    private ShoppingCart cart;  // Proxy delegates to new instance
}

Bean Lifecycle

Bean Lifecycle

Lifecycle Callbacks

@Service
public class CacheService {

    private Cache cache;

    // Preferred: JSR-250 annotations
    @PostConstruct
    public void init() {
        cache = new ConcurrentHashMap<>();
        loadInitialData();
    }

    @PreDestroy
    public void cleanup() {
        cache.clear();
        persistToDisk();
    }
}

// Alternative: Interface-based
@Service
public class ConnectionPool implements InitializingBean, DisposableBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        // Initialize connections
    }

    @Override
    public void destroy() throws Exception {
        // Close connections
    }
}

// Alternative: @Bean attributes
@Configuration
public class AppConfig {

    @Bean(initMethod = "init", destroyMethod = "cleanup")
    public ResourceManager resourceManager() {
        return new ResourceManager();
    }
}

Qualifiers and Primary

Multiple Beans of Same Type

public interface PaymentGateway {
    void process(Payment payment);
}

@Service
@Primary  // Default when multiple beans exist
public class StripeGateway implements PaymentGateway {
    @Override
    public void process(Payment payment) { }
}

@Service
@Qualifier("paypal")
public class PayPalGateway implements PaymentGateway {
    @Override
    public void process(Payment payment) { }
}

// Usage
@Service
public class PaymentService {

    @Autowired
    private PaymentGateway gateway;  // Gets StripeGateway (@Primary)

    @Autowired
    @Qualifier("paypal")
    private PaymentGateway paypalGateway;  // Gets PayPalGateway
}

Custom Qualifiers

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Premium {
}

@Service
@Premium
public class PremiumSupportService implements SupportService {
}

@Service
public class CustomerService {

    @Autowired
    @Premium
    private SupportService supportService;
}

Profiles

// Activate specific beans based on environment
@Configuration
@Profile("development")
public class DevConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .build();
    }
}

@Configuration
@Profile("production")
public class ProdConfig {

    @Bean
    public DataSource dataSource() {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl(System.getenv("DATABASE_URL"));
        return ds;
    }
}

// Activate profile
// application.properties: spring.profiles.active=production
// Command line: --spring.profiles.active=production
// Programmatic:
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.setAdditionalProfiles("production");
        app.run(args);
    }
}

Profile Expressions

@Profile("!production")           // NOT production
@Profile("development | test")    // development OR test
@Profile("cloud & aws")           // cloud AND aws

Conditional Beans

@Configuration
public class ConditionalConfig {

    @Bean
    @ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager();
    }

    @Bean
    @ConditionalOnMissingBean(CacheManager.class)
    public CacheManager noOpCacheManager() {
        return new NoOpCacheManager();
    }

    @Bean
    @ConditionalOnClass(name = "com.redis.RedisClient")
    public CacheManager redisCacheManager() {
        return new RedisCacheManager();
    }
}

Common Conditional Annotations

@ConditionalOnProperty     - Based on property value
@ConditionalOnClass        - Class exists on classpath
@ConditionalOnMissingClass - Class doesn't exist
@ConditionalOnBean         - Bean exists in context
@ConditionalOnMissingBean  - Bean doesn't exist
@ConditionalOnExpression   - SpEL expression evaluates to true
@ConditionalOnResource     - Resource exists on classpath

Lazy Initialization

@Component
@Lazy  // Bean created on first use, not at startup
public class ExpensiveService {

    public ExpensiveService() {
        // Heavy initialization
    }
}

// Lazy injection
@Service
public class SomeService {

    @Autowired
    @Lazy
    private ExpensiveService expensiveService;  // Proxy injected
}

// Global lazy initialization (Spring Boot)
spring.main.lazy-initialization=true

Events

// Custom event
public class OrderCreatedEvent extends ApplicationEvent {
    private final Order order;

    public OrderCreatedEvent(Object source, Order order) {
        super(source);
        this.order = order;
    }

    public Order getOrder() { return order; }
}

// Publishing events
@Service
public class OrderService {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void createOrder(Order order) {
        // Save order...
        eventPublisher.publishEvent(new OrderCreatedEvent(this, order));
    }
}

// Listening to events
@Component
public class NotificationListener {

    @EventListener
    public void handleOrderCreated(OrderCreatedEvent event) {
        sendNotification(event.getOrder());
    }

    @EventListener
    @Async  // Process asynchronously
    public void handleOrderCreatedAsync(OrderCreatedEvent event) {
        // Async processing
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void afterOrderCommitted(OrderCreatedEvent event) {
        // Only after transaction commits
    }
}

Common Interview Questions

  1. What is IoC and DI?
  2. IoC: Container controls object creation/lifecycle
  3. DI: Specific form of IoC where dependencies are injected

  4. Constructor vs Setter vs Field injection?

  5. Constructor: Recommended, immutable, testable
  6. Setter: Optional dependencies
  7. Field: Avoid, hard to test

  8. Singleton vs Prototype scope?

  9. Singleton: One instance per container
  10. Prototype: New instance each request

  11. @Component vs @Bean?

  12. @Component: Class-level, component scanning
  13. @Bean: Method-level, third-party classes

  14. How to inject prototype into singleton?

  15. Use ObjectFactory, Provider, or scoped proxy

  16. Bean lifecycle order?

  17. Constructor → DI → @PostConstruct → afterPropertiesSet → init-method

  • *