AOP Proxy: JDK Dynamic vs CGLIB
Bir ünlünün menajeri düşünün. Siz ünlüyle doğrudan konuşamazsınız — her şey menajeri üzerinden geçer. Menajeri telefonu açar, uygun görürse ünlüye bağlar, görüşme sonrası notları alır. Dışarıdan bakınca ünlüyle konuştuğunuzu sanırsınız ama aslında menajer araya girip ekstra işler yapmaktadır.
Spring AOP'deki proxy tam olarak bu menajerdir. Spring, @Aspect ile işaretlenen davranışları uygulamak için orijinal bean'in yerine bir proxy nesnesi oluşturur. Tüm metot çağrıları bu proxy üzerinden geçer — proxy gerekli advice'ları çalıştırır, sonra gerçek metodu çağırır.
Bu mekanizmayı anlamak kritiktir çünkü Spring AOP'nin en kafa karıştıran davranışları — self-invocation sorunu, final metot kısıtlaması, @Transactional'ın çalışmaması — hep proxy'nin nasıl çalıştığından kaynaklanır.
Proxy Nedir?
Proxy (vekil), başka bir nesnenin yerine geçerek onun adına işlem yapan nesnedir. Proxy Pattern, GoF Design Pattern'lerinden biridir.
Gerçek Dünya Analojileri
| Analoji | Gerçek Nesne | Proxy | Ek Davranış |
|---|---|---|---|
| Menajer | Ünlü | Menajer | Randevu kontrolü, ücret pazarlığı |
| Resepsiyon | Doktor | Resepsiyon | Randevu alma, sigorta kontrolü |
| CDN | Origin Server | Edge Server | Caching, coğrafi yönlendirme |
| Kredi Kartı | Banka hesabı | Kart terminali | Limit kontrolü, fraud detection |
Spring AOP'de:
Gerçek nesne = Service bean'iniz (
UserService)Proxy = Spring'in oluşturduğu sarmalayıcı nesne
Ek davranış =
@Before,@Around,@Afteradvice'ları
Proxy Nasıl Devreye Girer?
// Spring Container başlarken:
1. UserService bean oluşturulur
2. Spring, UserService'e uygulanacak aspect var mı kontrol eder
3. Aspect varsa → UserService'in PROXY'sini oluşturur
4. Container'da orijinal bean yerine PROXY kaydedilir
// Runtime'da:
Controller → @Autowired UserService
→ Aslında UserService PROXY'si inject edilir
→ Proxy, advice'ları çalıştırır
→ Sonra gerçek UserService'in metodunu çağırırBunu kodla görelim:
@RestController
public class UserController {
@Autowired
private UserService userService; // ← Bu proxy!
@PostConstruct
public void checkProxy() {
System.out.println(userService.getClass().getName());
// Çıktı: com.example.service.UserService$$SpringCGLIB$$0
// ↑ CGLIB proxy!
System.out.println(AopUtils.isAopProxy(userService)); // true
System.out.println(AopUtils.isCglibProxy(userService)); // true
}
}İki Proxy Mekanizması: JDK vs CGLIB
Spring AOP, proxy oluşturmak için iki mekanizma kullanır:
JDK Dynamic Proxy
Java'nın java.lang.reflect.Proxy sınıfını kullanır. Sadece interface'ler için çalışır:
// Interface
public interface UserService {
UserResponse findById(Long id);
UserResponse create(CreateUserRequest request);
}
// Implementation
@Service
public class UserServiceImpl implements UserService {
@Override
public UserResponse findById(Long id) { ... }
@Override
public UserResponse create(CreateUserRequest request) { ... }
}
// Spring, JDK Dynamic Proxy oluşturur:
// → Proxy, UserService interface'ini implement eder
// → Proxy, UserServiceImpl'ın metodlarını çağırırNasıl çalışır?
Caller → Proxy (UserService interface implement eder)
→ InvocationHandler.invoke()
→ Advice'ları çalıştır (@Before, @Around...)
→ target.method() çağır (gerçek UserServiceImpl)
→ Advice'ları çalıştır (@AfterReturning, @After...)
← Sonucu döndür
← Caller'a iletKısıtlamaları:
Sadece interface üzerinden çağrılar intercept edilir
Interface'i olmayan sınıflara uygulanamaz
instanceofkontrolü: proxy, sadece interface'e karşıtruedöner
CGLIB (Code Generation Library)
Bytecode manipulation ile hedef sınıfın subclass'ını oluşturur:
// Interface YOK — doğrudan sınıf
@Service
public class UserService {
public UserResponse findById(Long id) { ... }
public UserResponse create(CreateUserRequest request) { ... }
}
// Spring, CGLIB proxy oluşturur:
// → UserService$$SpringCGLIB$$0 extends UserService
// → Proxy, tüm public/protected metodları override ederNasıl çalışır?
Caller → Proxy (UserService'i extend eder)
→ MethodInterceptor.intercept()
→ Advice'ları çalıştır
→ super.method() çağır (parent = gerçek UserService)
→ Advice'ları çalıştır
← Sonucu döndür
← Caller'a iletKısıtlamaları:
`final` sınıflar proxy oluşturulamaz (subclass yapılamaz)
`final` metotlar override edilemez → advice çalışmaz
`private` metotlar override edilemez → advice çalışmaz
Subclass oluşturma, JDK proxy'den biraz daha yavaştır (ilk oluşturma)
Karşılaştırma Tablosu
| Özellik | JDK Dynamic Proxy | CGLIB |
|---|---|---|
| Gereksinim | Interface zorunlu | Interface zorunlu değil |
| Mekanizma | java.lang.reflect.Proxy | Bytecode generation (subclass) |
| Spring Boot varsayılanı | Hayır | Evet (2.0'dan beri) |
| final sınıf | N/A | ❌ Çalışmaz |
| final metot | N/A | ❌ Çalışmaz |
| private metot | ❌ | ❌ |
| İlk oluşturma hızı | Hızlı | Biraz yavaş |
| Çağrı hızı | Biraz yavaş | Hızlı |
| Memory | Az | Biraz fazla |
Spring Boot Varsayılanı
Spring Boot 2.0'dan itibaren spring.aop.proxy-target-class=true varsayılandır — yani CGLIB kullanılır:
# application.properties
# CGLIB (varsayılan) — interface olsa bile sınıf proxy'si oluşturur
spring.aop.proxy-target-class=true
# JDK Dynamic Proxy — sadece interface varsa proxy oluşturur
spring.aop.proxy-target-class=false💡 İpucu: Varsayılan CGLIB'de bırakın. JDK proxy'ye geçmenin pratikte bir avantajı yoktur. CGLIB, interface zorunluluğunu kaldırarak daha esnek ve modern bir yaklaşım sunar.
Self-Invocation Problemi — AOP'nin En Büyük Tuzağı
Bu, Spring AOP ile çalışan herkesin mutlaka karşılaşacağı ve anlaması gereken en önemli konudur.
Problem
@Service
public class OrderService {
@Transactional
public Order createOrder(CreateOrderRequest request) {
Order order = new Order(request);
orderRepository.save(order);
// Sipariş sonrası bildirimleri gönder
sendNotifications(order); // ❌ @Async ÇALIŞMAZ!
return order;
}
@Async
public void sendNotifications(Order order) {
emailService.sendOrderConfirmation(order);
smsService.sendOrderSms(order);
}
}sendNotifications() senkron olarak çalışacaktır — @Async etkisizdir. Neden?
Açıklama
Dışarıdan çağrı (Controller → Service):
Controller → Proxy.createOrder() → Advice çalışır → Real.createOrder()
↑ PROXY üzerinden ✅ @Transactional aktif
İç çağrı (createOrder → sendNotifications):
Real.createOrder() → this.sendNotifications()
↑ THIS = gerçek nesne, PROXY DEĞİL
❌ @Async çalışmaz!Mesele şu: this.sendNotifications() çağrısı proxy'yi bypass eder. this keyword'ü Java'da her zaman gerçek nesneyi referans eder — proxy'yi değil. Ve advice'lar sadece proxy üzerinden geçen çağrılarda tetiklenir.
Görsel Açıklama
┌─────────────────────────────────┐
│ PROXY │
│ ┌───────────────────────────┐ │
│ │ GERÇEK NESNE │ │
│ │ │ │
│ │ createOrder() { │ │
│ │ ... │ │
│ │ this.sendNotif() ──────┐ │ ← this = gerçek nesne
│ │ } │ │ │ proxy'yi bypass eder!
│ │ │ │ │
│ │ sendNotif() { ←────┘ │ │ ← Advice çalışmaz
│ │ ... │ │
│ │ } │ │
│ └───────────────────────────┘ │
└─────────────────────────────────┘
↑
Dışarıdan çağrı → Proxy üzerinden → Advice çalışır ✅Bu Sorun Neleri Etkiler?
Self-invocation problemi sadece @Async değil, proxy-based tüm mekanizmaları etkiler:
| Annotation | Self-Invocation'da | Açıklama |
|---|---|---|
@Transactional | ❌ Çalışmaz | Yeni transaction açılmaz |
@Cacheable | ❌ Çalışmaz | Cache kontrolü yapılmaz |
@Async | ❌ Çalışmaz | Senkron çalışır |
@Retryable | ❌ Çalışmaz | Retry yapılmaz |
| Custom AOP | ❌ Çalışmaz | Advice tetiklenmez |
Çözüm 1: Self-Injection
@Service
public class OrderService {
@Lazy
@Autowired
private OrderService self; // Proxy referansı
@Transactional
public Order createOrder(CreateOrderRequest request) {
Order order = new Order(request);
orderRepository.save(order);
self.sendNotifications(order); // ✅ Proxy üzerinden çağrı
return order;
}
@Async
public void sendNotifications(Order order) {
emailService.sendOrderConfirmation(order);
smsService.sendOrderSms(order);
}
}@Lazy annotation'ı circular dependency sorununu önler — Spring, inject sırasında lazy proxy oluşturur.
Çözüm 2: AopContext.currentProxy()
@Service
public class OrderService {
@Transactional
public Order createOrder(CreateOrderRequest request) {
Order order = new Order(request);
orderRepository.save(order);
// Mevcut proxy'yi al ve üzerinden çağır
((OrderService) AopContext.currentProxy())
.sendNotifications(order); // ✅ Proxy üzerinden
return order;
}
@Async
public void sendNotifications(Order order) { ... }
}Bu yöntem için exposeProxy ayarı gerekir:
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class Application { }⚠️ Dikkat: AopContext.currentProxy() cast gerektirir ve type-safe değildir. Self-injection yöntemi daha tercih edilir.
Çözüm 3: Ayrı Bean'e Çıkarmak (En Temiz)
@Service
@RequiredArgsConstructor
public class OrderService {
private final NotificationService notificationService;
@Transactional
public Order createOrder(CreateOrderRequest request) {
Order order = new Order(request);
orderRepository.save(order);
notificationService.sendOrderNotifications(order); // ✅ Farklı bean
return order;
}
}
@Service
public class NotificationService {
@Async
public void sendOrderNotifications(Order order) {
emailService.sendOrderConfirmation(order);
smsService.sendOrderSms(order);
}
}Bu en temiz çözümdür — Single Responsibility Principle'a da uyar.
final Sınıf ve Metot Kısıtlaması
CGLIB, subclass oluşturarak çalıştığı için final ile engellenebilir:
// ❌ final sınıf — CGLIB proxy oluşturamaz
@Service
public final class UserService {
public UserResponse findById(Long id) { ... }
}
// → BeanCreationException: Could not generate CGLIB subclass
// ❌ final metot — Proxy override edemez, advice çalışmaz
@Service
public class UserService {
@Transactional
public final UserResponse findById(Long id) { ... }
// → @Transactional ETKİSİZ — metot override edilemediği için
// proxy advice'ı uygulayamaz. Hata FIRLATILMAZ, sessizce çalışmaz!
}⚠️ Kritik: final metotta AOP çalışmaması hata fırlatmaz — sessizce devre dışı kalır. Bu, debug edilmesi çok zor bir sorundur. @Transactional final metotta çalışmazsa, transaction açılmaz ve veri tutarsızlığı oluşabilir.
Kotlin Dikkat
Kotlin'de tüm sınıflar ve metotlar varsayılan olarak final'dir. Spring Boot + Kotlin için kotlin-spring compiler plugin gereklidir — bu plugin, @Service, @Component, @Transactional gibi annotation'larla işaretlenmiş sınıfları otomatik olarak open yapar.
// Kotlin — varsayılan final
class UserService { // final!
fun findById(id: Long): User { ... } // final!
}
// kotlin-spring plugin ile:
// @Service class → open class
// @Transactional fun → open funProxy Davranışını Debug Etme
Proxy Olup Olmadığını Kontrol
@Component
public class ProxyDebugger implements CommandLineRunner {
@Autowired
private ApplicationContext context;
@Override
public void run(String... args) {
// Belirli bir bean'in proxy olup olmadığını kontrol et
Object userService = context.getBean("userService");
System.out.println("Class: " + userService.getClass().getName());
System.out.println("Is AOP Proxy: " + AopUtils.isAopProxy(userService));
System.out.println("Is CGLIB: " + AopUtils.isCglibProxy(userService));
System.out.println("Is JDK: " + AopUtils.isJdkDynamicProxy(userService));
System.out.println("Target class: " + AopUtils.getTargetClass(userService));
// Proxy'nin advice'larını listele
if (userService instanceof Advised advised) {
for (Advisor advisor : advised.getAdvisors()) {
System.out.println("Advisor: " + advisor.getAdvice().getClass().getName());
}
}
}
}Çıktı:
Class: com.example.service.UserService$$SpringCGLIB$$0
Is AOP Proxy: true
Is CGLIB: true
Is JDK: false
Target class: class com.example.service.UserService
Advisor: org.springframework.transaction.interceptor.TransactionInterceptor
Advisor: com.example.aspect.LoggingAspectSpring Debug Loglama
# application.properties
logging.level.org.springframework.aop=DEBUG
logging.level.org.springframework.cglib=DEBUGSpring AOP vs AspectJ — Tam Karşılaştırma
Spring AOP, proxy-based bir mekanizmadır. AspectJ ise derleme zamanı (compile-time) veya yükleme zamanı (load-time) weaving kullanır. Farkları:
| Özellik | Spring AOP (Proxy) | AspectJ (Weaving) |
|---|---|---|
| Mekanizma | Runtime proxy | Compile/Load-time weaving |
| Join Point | Sadece metot çağrısı | Metot, field, constructor, exception |
| Self-Invocation | ❌ Çalışmaz | ✅ Çalışır |
| final metot | ❌ Çalışmaz | ✅ Çalışır |
| private metot | ❌ Çalışmaz | ✅ Çalışır |
| Performance | Biraz overhead | Sıfır overhead (weaved) |
| Kurulum | Basit (starter yeterli) | Karmaşık (AspectJ compiler/weaver) |
| Spring bean gerekli mi? | Evet | Hayır |
| Öğrenme eğrisi | Düşük | Yüksek |
Ne zaman AspectJ?
Self-invocation sorunu çözülemiyorsa
Private metotlara aspect uygulamak gerekiyorsa
Non-Spring nesnelerine (new ile oluşturulan) aspect gerekiyorsa
Ekstra performance kritik ise
Gerçekte: Spring AOP, projelerin %95'i için yeterlidir. AspectJ'ye geçmek nadiren gerekir.
Proxy İle İlgili Yaygın Hatalar
1. ❌ instanceof Kontrolü Yanlış Çalışması
@Autowired
private UserService userService;
// CGLIB proxy'de:
System.out.println(userService instanceof UserService); // true ✅
System.out.println(userService instanceof UserServiceImpl); // Compile error (interface yok)
// JDK proxy'de (interface varsa):
System.out.println(userService instanceof UserService); // true ✅ (interface)
System.out.println(userService instanceof UserServiceImpl); // false ❌ (impl değil!)2. ❌ Field'lara Doğrudan Erişim
@Service
public class UserService {
public String serviceName = "UserService"; // public field
@Loggable
public void doSomething() { ... }
}
// Controller'da:
System.out.println(userService.serviceName);
// CGLIB proxy'de → null olabilir!
// Çünkü proxy subclass'tır, parent'ın field'ını inherit etmez💡 İpucu: Bean'lerin field'larına doğrudan erişmeyin, getter kullanın.
3. ❌ @PostConstruct'ta Proxy Olmaması
@Service
public class UserService {
@PostConstruct
public void init() {
this.doSomething(); // ❌ Proxy henüz tam oluşmamış olabilir
// @Transactional, @Cacheable çalışmayabilir
}
@Transactional
public void doSomething() { ... }
}
// ✅ Çözüm: ApplicationReadyEvent kullanın
@EventListener(ApplicationReadyEvent.class)
public void onReady() {
// Tüm proxy'ler hazır
self.doSomething(); // ✅ Güvenli
}Proxy Oluşturma Süreci (Detaylı)
Spring Container Başlatılıyor...
│
├─ 1. Bean tanımları (BeanDefinition) okunur
│
├─ 2. Bean nesneleri oluşturulur (constructor)
│
├─ 3. Dependency injection yapılır (@Autowired)
│
├─ 4. BeanPostProcessor'lar çalışır
│ │
│ └─ AbstractAutoProxyCreator (AOP)
│ │
│ ├─ Bu bean'e uygulanacak advisor (aspect) var mı?
│ │
│ ├─ Evet → Proxy oluştur
│ │ ├─ Interface var mı?
│ │ │ ├─ Evet + proxyTargetClass=false → JDK Dynamic Proxy
│ │ │ └─ Evet + proxyTargetClass=true → CGLIB (varsayılan)
│ │ └─ Interface yok → CGLIB
│ │
│ └─ Hayır → Orijinal bean'i döndür
│
├─ 5. @PostConstruct çalışır
│
└─ 6. Bean container'a kaydedilir (proxy veya orijinal)Özet
Spring AOP proxy-based çalışır — orijinal bean yerine proxy nesnesi oluşturulur
CGLIB Spring Boot'un varsayılanıdır — subclass oluşturarak çalışır, interface zorunlu değil
JDK Dynamic Proxy sadece interface'ler için çalışır, Spring Boot'ta artık varsayılan değil
Self-invocation AOP'nin en büyük tuzağıdır:
this.method()çağrısı proxy'yi bypass eder → çözüm: self-injection, ayrı bean, veyaAopContext`final` sınıf/metot CGLIB proxy oluşturulamaz/override edilemez — advice sessizce devre dışı kalır
Private metotlar proxy'lenemez — AOP çalışmaz
Debug için
AopUtils.isAopProxy(),Advisedinterface'i ve Spring AOP debug logging kullanınProjelerin %95'inde Spring AOP yeterlidir — AspectJ'ye geçiş nadiren gerekir
AI Asistan
Sorularını yanıtlamaya hazır