Key Spring Framework Concepts for Technical Interviews

Core Concepts: IoC and AOP

Spring is a robust Java platform designed to simplify the development of enterprise applications. Its primary advantages include:

  • Component Management: The framework handles the lifecycle and wiring of objects, removing the need for manual instantiation.
  • Proxy-based Logic: Utilizing dynamic proxies, Spring allows for reusable cross-cutting logic without code duplication.
  • Ecosystem Integration: It serves as the backbone for numerous other frameworks, offering seamless integration capabilities.
  • Low Intrusion: Application code remains clean with minimal dependency on Spring-specific APIs, often requiring only simple annotations.

Inversion of Control (IoC) is a design principle where the container takes responsibility for object creation and dependency injection. This can be achieved through annotations, constructor injection, or setter methods, eliminating the need for direct instantiation using new.

Aspect-Oriented Programming (AOP) complements IoC by enabling the modularization of cross-cutting concerns (like logging or security) via dynamic proxies. It allows developers to inject logic before or after method execution points.

Application Context Initialization

Understanding the startup sequence is crucial. While general steps involve reading configurations and creating beans, a deeper look reveals:

  1. Initialization of the reader and scanner components.
  2. Scanning the base packages to register BeanDefinition objects into the registry.
  3. Invoking the refresh() method to bootstrap the container.

The detailed flow includes:

  • Resource Location: Identifying configuration files (XML or Java config) via classes like AnnotationConfigApplicationContext.
  • Definition Loading: Parsing configurations and mapping them to internal data structures (BeanDefinition).
  • Bean Registration: Adding parsed definitions to the container map.
  • Instantiation & Wiring: Creating objects and injecting dependencies using constructors or setters.
  • Initialization: Calling custom init methods or @PostConstruct annotated methods.

Advanced insight: Spring performs definition merging before instantiation. Additionally, AOP proxying occurs after initialization via post-processors. Developers can hook into the lifecycle using application event listeners to execute logic at specific startup phases.

Bean Lifecycle Management

The lifecycle of a bean in Spring can be broken down into five distinct phases:

  1. Definition Registration: Metadata is captured during component scanning.
  2. Instantiation: The object is created, often using the default constructor, potentially modified by instantiation processors.
  3. Population: Dependencies are injected (e.g., via @Autowired).
  4. Initialization: Callback interfaces like InitializingBean (implementing afterPropertiesSet) or annotations like @PostConstruct are invoked.
  5. Destruction: When the context shuts down, methods annotated with @PreDestroy or implementing DisposableBean are called.

Thread Safety in Spring Beans

By default, Spring beans are singleton-scoped, which introduces thread safety concerns if they contain mutable state. To mitigate this:

  • Switch to prototype scope to create a new instance for every request.
  • Design beans to be stateless, avoiding instance variables that hold shared data.
  • Use ThreadLocal variables to bind state to a specific thread if necessary.

Resolving Circular Dependencies

Spring resolves circular dependencies (e.g., A depends on B, and B depends on A) using a three-level caching mechanism:

  • Level 1 Cache (singletonObjects): Stores fully initialized, ready-to-use singleton beans.
  • Level 2 Cache (earlySingletonObjects): Holds early references to beans that are being created but are not yet fully initialized.
  • Level 3 Cache (singletonFactories): Stores ObjectFactories that can produce early references, crucial for handling AOP proxies.

When an object is instantiated, its wrapped in an ObjectFactory and placed in the third cache. If a dependency requires an early reference, the factory is invoked. This is where AOP proxies are generated if needed.

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

The retrieval logic checks the caches sequentially to ensure the correct instance (raw or proxied) is returned:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

Transaction Management Strategies

Spring offers two approaches to transactions:

  • Programmatic: Using TransactionTemplate to manually control commit/rollback. This is verbose and rarely used unless fine-grained control is required.
  • Declarative: Using the @Transactional annotation, which is the standard approach. It allows defining propagation and isolation rules.

Propagation Levels:

  • REQUIRED: Joins current transaction or creates a new one.
  • REQUIRES_NEW: Suspends current transaction and creates a new one.
  • SUPPORTS: Supports transaction if exists, else runs non-transactional.
  • NOT_SUPPORTED: Runs non-transactional, suspending current transaction if any.
  • MANDATORY: Requires an existing transaction, throws exception otherwise.
  • NEVER: Ensures no transaction exists, throws exception if one is found.
  • NESTED: Executes within a nested transaction if a current transaction exists.

Isolation Levels:

  • READ_UNCOMMITTED: Allows dirty reads.
  • READ_COMMITTED: Prevents dirty reads, but allows non-repeatable reads.
  • REPEATABLE_READ: Prevents dirty and non-repeatable reads.
  • SERIALIZABLE: Full isolation via complete serialization.

Spring MVC Controller Scope

Controllers in Spring MVC are singletons by default. To ansure thread safety:

  1. Keep controllers stateless; avoid storing data in instance fields.
  2. If state is necessary, change the scope to prototype or request to create a new instance per HTTP request.

Tags: Spring Framework Spring Boot Inversion of Control Aspect-Oriented Programming Bean Lifecycle

Posted on Tue, 19 May 2026 16:29:25 +0000 by aubeasty