Question 14 · Section 2

What Proxy Types Exist?

Solutions: (1) self-injection: @Autowired private OrderService self; self.create(), (2) AopContext.currentProxy(), (3) refactoring — extract into a separate bean.

Language versions: English Russian Ukrainian

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

  1. JDK Proxy if interface exists
  2. CGLIB if no interface
  3. Avoid self-invocation
  4. 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