Які типи 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