Spring Container Full-Stack Walkthrough: 12 Interview Questions & Answers

This article provides a comprehensive Spring container walkthrough covering the full lifecycle—from bean definition scanning and proxy creation during startup to request handling, AOP interception, transaction and caching mechanisms—plus detailed answers to 12 common interview questions with code examples and best‑practice recommendations.

CodeSmart Hoops
CodeSmart Hoops
CodeSmart Hoops
Spring Container Full-Stack Walkthrough: 12 Interview Questions & Answers

Q1: Explain Spring's core mechanisms in a single request

Answer: The process is divided into two phases.

Startup phase (assembly) :

Calling refresh() triggers ConfigurationClassPostProcessor to scan annotations such as @Component and register BeanDefinition objects in the IOC container. preInstantiateSingletons instantiates each singleton bean, handling constructor creation, placing the object factory into the third‑level cache for circular‑dependency resolution, performing @Autowired injection, Aware callbacks, BeanPostProcessor pre‑processing, initialization, and post‑processing.

During BeanPostProcessor post‑processing, AnnotationAwareAspectJAutoProxyCreator scans for aspects and creates CGLIB/JDK dynamic proxies (AOP). finishRefresh() publishes ContextRefreshedEvent (event‑driven) and completes MessageSource setup (internationalization). DispatcherServlet.onRefresh registers HandlerMapping and HandlerAdapter (Spring MVC ready).

Request‑handling phase (runtime) :

The incoming request reaches DispatcherServlet.doDispatch, which finds the controller via HandlerMapping and invokes it through HandlerAdapter (MVC).

When orderService.create() is called, the container injects a proxy object, entering the AOP interception chain.

The TransactionInterceptor opens or joins a transaction according to the propagation settings.

The CacheInterceptor checks the cache; on a miss it proceeds to the method execution.

The service calls a MyBatis/JPA mapper to execute SQL (data access).

After a successful order, publishEvent(OrderCreatedEvent) triggers an asynchronous SMS via @EventListener (event‑driven).

If an exception occurs, @RestControllerAdvice together with MessageSource translates the error; otherwise the transaction commits and a JSON response is returned.

Core conclusion: Startup builds proxies; runtime walks the interception chain where transaction, cache and custom aspects act as nodes.

Q2: Why does @Transactional not work on private, final, or static methods?

Answer: @Transactional relies on AOP proxy interception. Spring creates either a CGLIB subclass proxy or a JDK dynamic proxy for interfaces. The proxy mechanism requires the method to be overridable:

Private methods: JVM does not allow subclass overriding; CGLIB cannot intercept, so the annotation is ineffective.

Final methods: Final methods cannot be overridden; the proxy cannot insert interception logic.

Static methods: Static methods belong to the class and are not dispatched through the proxy instance, thus cannot be intercepted.

The root cause is the proxy mechanism’s requirement for method overridability, not a limitation of Spring itself.

Q3: Why does a @Transactional method not work when called internally within the same service, and how to fix it?

Why it fails: Inside the service, this.b() invokes the original object (or the proxy itself) directly, bypassing the proxy dispatch. Since the transaction is applied by the proxy, the internal call does not trigger it.

Solutions:

Split the methods into two separate beans; inject the second bean and call its method so the call goes through a proxy.

Self‑injection: @Autowired private OrderService self; then call self.b() (watch for circular dependencies, add @Lazy if needed).

Obtain the current proxy via ((OrderService) AopContext.currentProxy()).b() after enabling @EnableAspectJAutoProxy(exposeProxy = true).

Move @Transactional to the outer method (A) if A and B belong to the same transaction.

Q4: Why does Spring use a three‑level cache to resolve circular dependencies?

Spring’s caches:

Level 1 – singletonObjects : Fully initialized bean instances.

Level 2 – earlySingletonObjects : Partially created beans (instantiated but not fully initialized).

Level 3 – singletonFactories : ObjectFactory callbacks that can create the bean on demand.

The third level is needed because proxy creation (AOP) must be deferred until after BeanPostProcessor post‑processing. If only two levels existed, Spring would have to decide immediately after bean instantiation whether a proxy is required, breaking the design that proxy generation occurs later. With the third‑level cache, the ObjectFactory (a lambda) is stored and invoked only when another bean actually needs the reference, allowing the decision about proxy creation to be delayed.

Q5: Is the third‑level cache used when there is no circular dependency?

No. The ObjectFactory in the third‑level cache is invoked only when a bean is currently being created ( isSingletonCurrentlyInCreation) and another bean references it. Without circular dependencies, each bean finishes creation, moves to the first‑level cache, and other beans obtain it directly from there, so the third‑level callback is never called. The factories are still registered via addSingletonFactory, but they remain unused.

Q6: Relationship and execution order of @Transactional and @Cacheable on the same method

Both interceptors are nodes on the same AOP chain and share the same proxy. Execution order is determined by the interceptor order (configurable via @Order or the Ordered interface):

By default, the transaction interceptor ( @Transactional) is outermost, and the cache interceptor ( @Cacheable) is inner, so the transaction starts first, then the cache is checked.

If the cache hits, the method body is skipped and the transaction commits an empty transaction.

If the cache misses, the method executes, accesses the database, writes to the cache, and finally the transaction commits.

Note: Cache write (via @CachePut or a cache miss) occurs before the transaction commits. To guarantee consistency, use @CacheEvict after commit or a transaction synchronizer that writes the cache in an afterCommit callback.

Q7: Is @EventListener synchronous or asynchronous by default, and how to decouple it from the main transaction?

Default: Synchronous – it runs in the same thread and transaction context as the publisher; an exception will cause the main transaction to roll back.

Decoupling methods:

Annotate with @EventListener + @Async for asynchronous execution in a separate thread and transaction.

Use @TransactionalEventListener(phase = AFTER_COMMIT) for synchronous execution that is delayed until after the main transaction commits (strong consistency).

Use @TransactionalEventListener(phase = AFTER_ROLLBACK) to run only when the transaction rolls back (compensation logic).

Recommendation table (converted to list):

Scenario: Send SMS after order success (strong consistency) → @TransactionalEventListener(AFTER_COMMIT) + @Async Simple notification → @EventListener + @Async Logic that must roll back with the main transaction → default @EventListener (synchronous)

Q8: Why does AOP intercept calls made via @Autowired but not self‑invocations (this.method())?

The container stores proxy objects generated in the BeanPostProcessor post‑processing stage. @Autowired injects this proxy, so method calls go through the proxy and are intercepted. A self‑invocation uses this, which refers to the original target object, bypassing the proxy and therefore not intercepted. This is an inherent limitation of proxy‑based AOP: only external calls can be intercepted.

Q9: Are MyBatis second‑level cache and Spring’s @Cacheable the same? Can they be used together?

They are independent caching mechanisms:

MyBatis second‑level cache: Operates at the mapper (SQL result) layer, keyed by statement ID + parameters, cleared by update/insert/delete, implemented by MyBatis.

Spring @Cacheable: Operates at any business method level, keyed by SpEL expressions, cleared explicitly with @CacheEvict, implemented by Spring AOP + CacheManager.

Both can be used simultaneously (e.g., cache business objects with @Cacheable and let MyBatis cache query results), but care must be taken to keep the two caches consistent—updates should evict entries from both caches, and many teams prefer using only one layer to avoid stale data.

Q10: Execution order of Filter, HandlerInterceptor, and AOP for a single HTTP request

Order: Filter → HandlerInterceptor → AOP → Business method, with the return path in reverse.

Detailed flow (pre‑formatted):

Request
 ├─ Filter.doFilter (Servlet container, may be multiple, ordered)
 │   ├─ HandlerInterceptor.preHandle (Spring MVC)
 │   │   ├─ AOP interception chain enters proxy
 │   │   │   ├─ TransactionInterceptor.before (transaction start)
 │   │   │   ├─ CacheInterceptor (cache check)
 │   │   │   ├─ Custom @Around aspect
 │   │   │   ├─ Business method execution
 │   │   │   ├─ Custom aspect after‑processing
 │   │   │   ├─ CacheInterceptor after‑processing
 │   │   │   └─ TransactionInterceptor.after (commit/rollback)
 │   │   └─ HandlerInterceptor.postHandle
 │   └─ Filter processes response
 └─ Response returned

Note: Filters belong to the Servlet specification (outside DispatcherServlet), HandlerInterceptors belong to Spring MVC (inside DispatcherServlet), and AOP operates within the proxy layer (inside the handler method).

Q11: Relationship between @Transactional rollback and EntityManager.flush in JPA

flush: Synchronizes dirty entities from the persistence context (first‑level cache) to the database by issuing SQL, but does not commit the transaction.

commit: Transaction manager commits the transaction, making flushed changes permanent.

rollback: Transaction rollback undoes database changes; any SQL already flushed will be rolled back because the transaction has not been committed.

Key points:

By default JPA automatically flushes before transaction commit (FlushMode AUTO, also flushes before queries).

@Transactional rolls back on unchecked exceptions (RuntimeException, Error) by default; checked exceptions do not trigger rollback unless configured.

If flush fails (e.g., constraint violation), an exception is thrown immediately, causing the surrounding transaction to roll back.

Tip: Manually calling entityManager.flush() can expose errors early or obtain generated IDs, but the changes are still invisible to other transactions until commit.

Q12: Roles of IOC, AOP, Transaction, and Cache during container startup and runtime

IOC

Startup: Scans BeanDefinitions, instantiates all singleton beans, and wires dependencies.

Runtime: Provides already‑wired beans to callers (e.g., @Autowired receives the fully built bean).

AOP

Startup: During BeanPostProcessor post‑processing, scans for aspects and creates proxy objects, storing them in the container.

Runtime: Proxies intercept method calls, executing the interceptor chain (transaction, cache, custom aspects).

Transaction

Startup: Configures TransactionManager and registers TransactionInterceptor.

Runtime: TransactionInterceptor opens, commits, or rolls back transactions around method execution.

Cache

Startup: Configures CacheManager, registers CacheInterceptor, and optionally pre‑loads caches.

Runtime: CacheInterceptor checks the cache, returns cached results on hit, or writes to the cache after method execution.

Core conclusion: Startup builds the infrastructure (bean definitions, proxies, interceptors); runtime simply triggers the pre‑assembled components. Understanding this explains why Spring starts slowly but runs fast, why annotation changes require a restart, and why @Autowired already provides a proxy object.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendcacheTransactionAOPiocSpringMyBatisinterviewSpring FrameworkJPA
CodeSmart Hoops
Written by

CodeSmart Hoops

A working programmer who loves coding and basketball. By day I debug code; by night I dissect tactics. I write articles to document my journey, focusing on Java, AI, Python and other programming topics, with occasional posts about basketball, English, and books. Hope it's helpful—thanks for following and support.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.