Какие типы Proxy существуют?
Решения: (1) self-injection: @Autowired private OrderService self; self.create(), (2) AopContext.currentProxy(), (3) рефакторинг — вынести в отдель бин.
🟢 Junior Level
Proxy — объект-заместитель, который контролирует доступ к другому объекту.
Простая аналогия: Секретарь не делает работу директора, но контролирует, кто может к нему попасть.
Пример:
// Реальный объект
interface Image { void display(); }
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk(); // Дорогая операция!
}
private void loadFromDisk() {
System.out.println("Loading: " + filename);
}
public void display() {
System.out.println("Displaying: " + filename);
}
}
// Proxy — загружает только когда нужно
class ProxyImage implements Image {
private String filename;
private RealImage realImage; // Ещё не создан!
public ProxyImage(String filename) {
this.filename = filename;
}
public void display() {
// Ленивая загрузка
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// Использование
Image image = new ProxyImage("photo.jpg"); // Быстро
image.display(); // Только тут загружается!
🟡 Middle Level
Типы Proxy
1. Virtual Proxy (ленивая загрузка):
// Загрузка только по требованию
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 (контроль доступа):
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 (удалённый доступ):
// RMI, gRPC — прокси для удалённых объектов
interface RemoteService { String getData(); }
class RemoteServiceProxy implements RemoteService {
public String getData() {
// Сетевой вызов к удалённому сервису
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
// Прокси создаётся на runtime
interface BusinessService { void process(); }
class RealService implements BusinessService {
public void process() { System.out.println("Processing..."); }
}
// Создание прокси
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
В современных JDK (8u40+) разница минимальна — JDK Proxy использует MethodHandle/invokedynamic.
| Критерий | JDK Dynamic Proxy | CGLIB |
|---|---|---|
| Требует | Интерфейс | Наследование |
| Скорость создания | Быстро | Медленно |
| Скорость вызова | ~равна CGLIB (MethodHandle) | ~равна JDK Proxy |
| final методы | Н/Д | Не может переопределить |
Spring Proxy выбор
// Spring Framework: JDK Proxy (если есть интерфейс).
// Spring Boot 2.x+: proxyTargetClass=true по умолчанию → CGLIB.
@Service
public class UserServiceImpl implements UserService { }
// → Spring Framework: JDK Dynamic Proxy
// → Spring Boot 2.x+: CGLIB (proxyTargetClass=true по умолчанию)
// forceAutoProxy = CGLIB
@Service
public class UserService { } // Без интерфейса
// → CGLIB Proxy (наследование)
@Transactional как Proxy
@Transactional
public void transfer() {
// Spring оборачивает метод в:
// 1. BEGIN TRANSACTION
// 2. Вызов реального метода
// 3. COMMIT / ROLLBACK
}
Production Experience
Реальный сценарий: Self-invocation проблема
@Service
public class OrderService {
@Transactional
public void create() { /* ... */ }
public void createBatch() {
for (int i = 0; i < 10; i++) {
this.create(); // ❌ Proxy не сработает!
}
}
}
Решения: (1) self-injection: @Autowired private OrderService self; self.create(),
(2) AopContext.currentProxy(), (3) рефакторинг — вынести в отдель бин.
Best Practices
- JDK Proxy если есть интерфейс
- CGLIB если нет интерфейса
- Избегайте self-invocation
- Proxy overhead в Hot-path
Резюме для Senior
- JDK Proxy = интерфейсы, в современных JDK скорость ~равна CGLIB (MethodHandle)
- CGLIB = наследование
- Spring = автоматически выбирает тип (Boot 2.x+: CGLIB по умолчанию)
- Self-invocation = proxy не работает
🎯 Шпаргалка для интервью
Обязательно знать:
- Proxy — объект-заместитель, контролирующий доступ к другому объекту (логирование, кэш, безопасность, lazy loading)
- JDK Dynamic Proxy требует интерфейс, CGLIB работает через наследование (не может переопределить final методы)
- Spring Framework: JDK Proxy если есть интерфейс; Spring Boot 2.x+: CGLIB по умолчанию (proxyTargetClass=true)
- @Transactional, @Cacheable, @Async — всё это Proxy под капотом
- Self-invocation проблема: вызов this.method() внутри бина не проходит через Proxy
- Virtual Proxy — ленивая загрузка, Protection Proxy — контроль доступа, Remote Proxy — удалённый доступ
- В современных JDK разница в скорости JDK Proxy vs CGLIB минимальна (MethodHandle/invokedynamic)
Частые уточняющие вопросы:
- Почему @Transactional не работает при вызове this? — this пропускает Proxy, нужен self-injection или AopContext.currentProxy()
- Когда Spring выберет CGLIB вместо JDK Proxy? — Когда нет интерфейса или proxyTargetClass=true (Boot 2.x+)
- Чем Proxy отличается от Decorator? — Proxy контролирует доступ к объекту, Decorator добавляет функциональность
- Какой overhead у Proxy? — Дополнительный уровень косвенности, в современных JDK минимальный (MethodHandle)
Красные флаги (НЕ говорить):
- “Proxy всегда медленнее прямого вызова” — в современных JDK разница минимальна
- “Я могу использовать @Transactional на private методе” — Proxy не работает на private методах
- “this.create() вызовет @Transactional” — self-invocation проблема, Proxy не сработает
- “CGLIB всегда быстрее JDK Proxy” — в современных JDK скорость примерно равна
Связанные темы:
- [[12. В чём преимущество Decorator перед наследованием]] — Proxy vs Decorator
- [[11. Как реализован Observer в Java]] — Spring Events через Proxy
- [[2. Какие категории паттернов существуют]] — Structural паттерны
- [[16. Какие антипаттерны вы знаете]] — Poltergeist (лишний уровень косвенности)
- [[1. Что такое паттерны проектирования]] — Proxy в Spring AOP