Proxy Pattern — an object that controls access to another object (the real one). Spring uses proxies everywhere: @Transactional, @Async, @Cacheable, @PreAuthorize.
Simple analogy: A proxy is like a director's secretary. You call the secretary (proxy), not the director (real object). The secretary checks if you can be let in (security), opens the door (transaction), lets you talk to the director, then closes the door (commit/rollback). You never interact with the director directly — the secretary handles all the overhead.
Why it matters: Spring's "magic" — transactions, security, caching — is implemented through proxies. When you add @Transactional, Spring does not modify your class. It wraps it in a proxy that intercepts every method call, starts a transaction before the call, and commits/rolls back after. Understanding proxies explains why some Spring features "silently fail" and how to fix them.
How Spring creates proxies:
1@Service2public class UserService {3 @Transactional // Spring creates a proxy around this class4 public void updateUser(Long id, User user) {5 // Your code6 userRepository.save(user);7 }8}910// What Spring does (simplified):11// UserService$$EnhancerBySpringCGLIB$$abc123 — this is the proxy12// On invocation:13// 1. Open transaction14// 2. Call the original method15// 3. Commit or rollback
Two types of proxies:
java.lang.reflect.Proxy). The proxy implements the same interface.1// JDK proxy example2public interface UserService {3 void updateUser(Long id, User user);4}56// CGLIB proxy example — no interface needed7@Service8public class UserServiceImpl {9 @Transactional10 public void updateUser(Long id, User user) { ... }11}
Self-invocation problem (the #1 proxy pitfall):
1@Service2public class UserService {3 public void methodA() {4 methodB(); // Called via this, NOT through the proxy!5 }67 @Transactional8 public void methodB() { ... }9 // @Transactional will NOT work for methodA() → methodB()10}
Why it fails: methodA() calls this.methodB(), which goes directly to the real object, bypassing the proxy. The transaction interceptor never sees the call.
Solutions:
1// Solution 1: Self-inject2@Service3public class UserService {4 @Autowired5 private UserService self; // This is actually the proxy!67 public void methodA() {8 self.methodB(); // Through the proxy!9 }10}1112// Solution 2: Extract transactional method to a separate bean13@Service14public class UserService {15 @Autowired16 private UserTransactionHelper helper;1718 public void methodA() {19 helper.methodB(); // Different bean → goes through proxy20 }21}
Proxy in Spring Security: Every bean in Spring is wrapped in a proxy by default for @PreAuthorize, @Secured, etc. This is why you can secure methods declaratively — the proxy checks permissions before forwarding to the real method.
Common pitfalls:
final classes cannot be proxied — CGLIB works by subclassing. final prevents subclassing, so @Transactional on a final class is silently ignored.