Question 13 · Section 5

What is a Proxy in Spring?

4. AopUtils.getTargetClass() -> for reflection 5. Avoid final on proxied methods 6. Spring Boot -> CGLIB by default

Language versions: English Russian Ukrainian

Junior Level

Proxy is an intermediary object that Spring creates around your bean.

Simple analogy: A receptionist. You want to talk to the director, but first you go through the receptionist who checks your documents, records your appointment, etc.

@Service
public class UserService {
    @Transactional  // Spring will create a Proxy around this bean
    public void save(User user) { }
}

// You receive not UserService, but a Proxy!
// Proxy -> opens transaction -> calls save() -> closes transaction

Why:

  • Adds functionality without changing your code
  • @Transactional, @Async, @Cacheable - all work through proxies

Middle Level

Two Types of Proxies

JDK Dynamic Proxy:

// Via interfaces
public interface UserService { void save(User user); }

// Spring creates:
class UserServiceProxy implements UserService {
    private UserService target;
    public void save(User user) {
        // Before call
        target.save(user);
        // After call
    }
}

CGLIB:

// Via inheritance
class UserServiceProxy extends UserService {
    // Overrides methods
}

// Cannot proxy final classes/methods!

Spring Boot Defaults

Spring Boot 2.x/3.x: proxy-target-class=true by default, so CGLIB is always used, even if interfaces exist. Spring 1.x: JDK Dynamic Proxy by default.

Why:
  -> Fewer type problems
  -> Can inject by class, not by interface

Self-invocation

@Service
public class UserService {
    public void outer() {
        inner();  // @Transactional will NOT work!
    }

    @Transactional
    public void inner() { }
}

// outer() -> inner() goes directly, around the proxy!

Senior Level

Interceptor Chain

Method call on proxy:
  1. SecurityInterceptor -> permission check
  2. TransactionInterceptor -> open transaction
  3. CacheInterceptor -> check cache
  4. Target method -> your code
  5. Commit/Rollback
  6. Return result

-> Each interceptor = one Advice
-> Order determined by @Order

How to Check for a Proxy

// In debugger:
// JDK: com.sun.proxy.$Proxy123
// CGLIB: UserService$$EnhancerBySpringCGLIB$$abc123

// Programmatically:
AopUtils.isAopProxy(bean);
AopUtils.isCglibProxy(bean);
AopUtils.getTargetClass(proxy);  // when you work with an object but don't know if it's a proxy or original (e.g., when comparing types via instanceof or getDeclaredMethod).

Production Experience

Real scenario: final method not proxied

@Service
public class UserService {
    @Transactional
    public final void save(User user) { }  // final!
}

// CGLIB creates a subclass and overrides methods.
// final methods cannot be overridden, so the call goes
// directly - around proxy logic (transactions, logging, etc.)

// Transaction is NOT created!
// -> Data saved WITHOUT transaction
// -> Bug in production!

Best Practices

  1. JDK Proxy -> via interfaces
  2. CGLIB -> via inheritance (not final!)
  3. Self-invocation -> transaction does not work
  4. AopUtils.getTargetClass() -> for reflection
  5. Avoid final on proxied methods
  6. Spring Boot -> CGLIB by default

Summary for Senior

  • Proxy = wrapper for AOP
  • JDK = interfaces, CGLIB = inheritance
  • Spring Boot -> CGLIB always
  • Self-invocation = call around proxy
  • final methods -> not proxied by CGLIB
  • Interceptor chain -> order via @Order

Interview Cheat Sheet

Must know:

  • Proxy = wrapper object that adds AOP functionality without changing code
  • Two types: JDK Dynamic Proxy (via interfaces) and CGLIB (via inheritance)
  • Spring Boot 2.x/3.x: proxy-target-class=true by default -> always CGLIB
  • @Transactional, @Async, @Cacheable - all work through proxies
  • Self-invocation: calling this.method() goes directly, bypassing proxy
  • Final classes and final methods CANNOT be proxied via CGLIB
  • Interceptor chain: Security -> Transaction -> Cache -> Target method

Common follow-up questions:

  • How to check if a bean is a proxy? -> AopUtils.isAopProxy(bean), AopUtils.isCglibProxy(bean)
  • Why doesn’t self-invocation work? -> this refers to Target, not Proxy
  • How to get the original class from a proxy? -> AopUtils.getTargetClass(proxy)
  • What is the difference between JDK Proxy and CGLIB? -> JDK requires interface, CGLIB creates a subclass

Red flags (DO NOT say):

  • “CGLIB can proxy final methods” - cannot, inheritance is impossible
  • “Self-invocation works with CGLIB” - does not work, this = Target
  • “Proxy adds significant overhead” - nanoseconds, negligible
  • “Spring creates a proxy for every bean” - only with AOP annotations

Related topics:

  • [[14. When does Spring create a proxy]]
  • [[15. What is AOP Aspect-Oriented Programming]]
  • [[18. Why Transactional does not work with self-invocation]]
  • [[19. How to solve the self-invocation problem]]