Under The Hood
backend springboot Spring Framework 6.x / Boot 3.x

Spring bean lifecycle and proxies

Last updated
Prerequisites:
Spring Boot basics
Understanding of dependency injection and annotations
  • spring
  • springboot
  • ioc
  • proxy
  • aop
  • bean-lifecycle

Read at your depth

The practical view

Spring creates beans via the ApplicationContext: @Component/@Service/@Repository classes are instantiated, dependencies injected, then post-processed. The bean lifecycle is: instantiation → populate properties → @PostConstruct / InitializingBean.afterPropertiesSet → proxy wrapping (if needed) → ready. @PreDestroy / DisposableBean.destroy runs on shutdown. AOP advice (@Transactional, @Secured, @Async, custom @Aspect) is applied by wrapping the bean in a proxy: Spring creates either a JDK dynamic proxy (interface-based) or a CGLIB subclass proxy (class-based). Method calls on the injected reference go through the proxy, which applies the advice, then delegates to the real bean.

Legacy vs modern

Transaction via self-invocation vs via the proxied reference

Calling a @Transactional method through this bypasses the proxy and silently loses the transaction; going through the injected proxy applies the advice.

before → after
Self-invocation
@Service
class OrderService {
  public void place(Order o) {
    saveAndCharge(o); // this.saveAndCharge → raw bean, no proxy
  }
  @Transactional
  void saveAndCharge(Order o) { ... }
}
Proxied reference
@Service
class OrderService {
  public void place(Order o) {
    self().saveAndCharge(o); // through the proxy
  }
  @Transactional
  void saveAndCharge(Order o) { ... }
}

Interview gotchas

Context

This is the classic Spring proxy question — the interviewer wants the mechanism (proxy wrapping, self-invocation bypass), not just 'it doesn't work'.

The mechanical answer

Spring wraps the bean in a proxy whose interceptor chain contains the transaction advisor. Any external caller holds the proxy, so calls flow through TransactionInterceptor, which begins/commits/rolls back. A call to this.method() resolves against the raw target instance — this is never the proxy — so the invocation never enters the interceptor chain and no transaction is created. The failure is silent because the method body still runs (the missing piece is the boundary). Fixes: self-injection of the proxy, moving the method to a collaborating bean, or using TransactionTemplate (programmatic, no proxy needed).

Trap

The naive answer is 'make the method public' — visibility is not the cause; proxy interceptor chains apply to public methods but the bypass is about the invocation target, not visibility. Another trap: claiming the transaction rolls back on checked exceptions by default — default rollback is only on RuntimeException/Error, which is a separate classic gotcha candidates should name. Combining both shows real Spring experience.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.