What Proxy Types Exist?
Solutions: (1) self-injection: @Autowired private OrderService self; self.create(), (2) AopContext.currentProxy(), (3) refactoring — extract into a separate bean.
Junior Level
Proxy is a surrogate object that controls access to another object.
Simple analogy: A secretary doesn’t do the director’s work, but controls who can reach them.
Example:
// Real object
interface Image { void display(); }
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk(); // Expensive operation!
}
private void loadFromDisk() {
System.out.println("Loading: " + filename);
}
public void display() {
System.out.println("Displaying: " + filename);
}
}
// Proxy — loads only when needed
class ProxyImage implements Image {
private String filename;
private RealImage realImage; // Not yet created!
public ProxyImage(String filename) {
this.filename = filename;
}
public void display() {
// Lazy loading
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// Usage
Image image = new ProxyImage("photo.jpg"); // Fast
image.display(); // Only loads here!
Middle Level
Proxy Types
1. Virtual Proxy (lazy loading):
// Load only on demand
class LazyLoader<T> {
private final Supplier<T> factory;
private T instance;
public T get() {
if (instance == null) {
instance = factory.get();
}
return instance;
}
}
2. Protection Proxy (access control):
class ProtectedDocument {
private final String content;
private final String owner;
public void read(String user) {
if (!user.equals(owner)) {
throw new SecurityException("Access denied");
}
System.out.println(content);
}
}
3. Remote Proxy (remote access):
// RMI, gRPC — proxy for remote objects
interface RemoteService { String getData(); }
class RemoteServiceProxy implements RemoteService {
public String getData() {
// Network call to remote service
return httpClient.get("http://remote-service/data");
}
}
4. Logging Proxy:
class LoggingProxy implements UserService {
private final UserService real;
private final Logger log;
public User findById(Long id) {
log.info("findById(" + id + ")");
return real.findById(id);
}
}
JDK Dynamic Proxy
// Proxy created at runtime
interface BusinessService { void process(); }
class RealService implements BusinessService {
public void process() { System.out.println("Processing..."); }
}
// Creating proxy
BusinessService proxy = (BusinessService) Proxy.newProxyInstance(
BusinessService.class.getClassLoader(),
new Class<?>[] { BusinessService.class },
(proxyObj, method, args) -> {
System.out.println("Before: " + method.getName());
Object result = method.invoke(new RealService(), args);
System.out.println("After: " + method.getName());
return result;
}
);
proxy.process();
// Before: process
// Processing...
// After: process
Senior Level
JDK Proxy vs CGLIB
In modern JDK (8u40+), the difference is minimal — JDK Proxy uses MethodHandle/invokedynamic.
| Criterion | JDK Dynamic Proxy | CGLIB |
|---|---|---|
| Requires | Interface | Inheritance |
| Creation speed | Fast | Slow |
| Call speed | ~= CGLIB (MethodHandle) | ~= JDK Proxy |
| final methods | N/A | Cannot override |
Spring Proxy Selection
// Spring Framework: JDK Proxy (if interface exists).
// Spring Boot 2.x+: proxyTargetClass=true by default -> CGLIB.
@Service
public class UserServiceImpl implements UserService { }
// -> Spring Framework: JDK Dynamic Proxy
// -> Spring Boot 2.x+: CGLIB (proxyTargetClass=true by default)
// forceAutoProxy = CGLIB
@Service
public class UserService { } // Without interface
// -> CGLIB Proxy (inheritance)
@Transactional as Proxy
@Transactional
public void transfer() {
// Spring wraps the method in:
// 1. BEGIN TRANSACTION
// 2. Call real method
// 3. COMMIT / ROLLBACK
}
Production Experience
Real scenario: Self-invocation problem
@Service
public class OrderService {
@Transactional
public void create() { /* ... */ }
public void createBatch() {
for (int i = 0; i < 10; i++) {
this.create(); // Proxy won't work!
}
}
}
Solutions: (1) self-injection: @Autowired private OrderService self; self.create(),
(2) AopContext.currentProxy(), (3) refactoring — extract into a separate bean.
Best Practices
- JDK Proxy if interface exists
- CGLIB if no interface
- Avoid self-invocation
- Proxy overhead in hot paths
Senior Summary
- JDK Proxy = interfaces, in modern JDK speed ~= CGLIB (MethodHandle)
- CGLIB = inheritance
- Spring = auto-selects type (Boot 2.x+: CGLIB by default)
- Self-invocation = proxy doesn’t work
Interview Cheat Sheet
Must know:
- Proxy — surrogate object controlling access to another object (logging, caching, security, lazy loading)
- JDK Dynamic Proxy requires an interface, CGLIB works via inheritance (cannot override final methods)
- Spring Framework: JDK Proxy if interface exists; Spring Boot 2.x+: CGLIB by default (proxyTargetClass=true)
- @Transactional, @Cacheable, @Async — all use Proxy under the hood
- Self-invocation problem: calling this.method() inside a bean bypasses Proxy
- Virtual Proxy — lazy loading, Protection Proxy — access control, Remote Proxy — remote access
- In modern JDK, JDK Proxy vs CGLIB speed difference is minimal (MethodHandle/invokedynamic)
Common follow-up questions:
- Why doesn’t @Transactional work with this calls? — this bypasses Proxy, need self-injection or AopContext.currentProxy()
- When does Spring choose CGLIB over JDK Proxy? — When no interface exists or proxyTargetClass=true (Boot 2.x+)
- How does Proxy differ from Decorator? — Proxy controls access to object, Decorator adds functionality
- What is Proxy overhead? — Additional level of indirection, minimal in modern JDK (MethodHandle)
Red flags (DO NOT say):
- “Proxy is always slower than direct call” — in modern JDK the difference is minimal
- “I can use @Transactional on a private method” — Proxy doesn’t work on private methods
- “this.create() will trigger @Transactional” — self-invocation problem, Proxy won’t work
- “CGLIB is always faster than JDK Proxy” — in modern JDK speeds are approximately equal
Related topics:
- [[12. Advantage of Decorator over inheritance]] — Proxy vs Decorator
- [[11. How is Observer implemented in Java]] — Spring Events via Proxy
- [[02. What pattern categories exist]] — Structural patterns
- [[16. What anti-patterns do you know]] — Poltergeist (excessive indirection)
- [[01. What are design patterns]] — Proxy in Spring AOP