Pointcut Expressions
Bir askeri operasyonda "hedef belirleme" kritiktir. Yanlış hedef belirlerseniz, istemediğiniz şeylere müdahale edersiniz. Çok geniş hedef belirlerseniz kaynakları boşa harcarsınız. Çok dar belirlerseniz asıl hedefi kaçırabilirsiniz. İdeal olan: tam doğru hedefi, minimum ifadeyle tanımlamak.
Spring AOP'deki pointcut expressions tam olarak bu hedef belirleme sistemidir. Advice'ın hangi metotlara uygulanacağını belirler. execution(), within(), @annotation() gibi designator'lar ile tek bir ifadeyle binlerce metodu hedefleyebilir veya tek bir metodu pinpoint edebilirsiniz.
Bu derste pointcut sözdizimini, tüm designator'ları, birleştirme operatörlerini ve named pointcut'lar ile yeniden kullanılabilirlik stratejilerini öğreneceksiniz.
Pointcut Nedir?
Pointcut, "hangi join point'lerde advice'ın çalışacağını" tanımlayan bir ifadedir.
@Before("execution(* com.example.service.*.*(..))")
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Bu kısım = Pointcut Expression
public void logEntry(JoinPoint jp) { ... }Join Point: Advice'ın potansiyel olarak uygulanabileceği nokta (Spring AOP'de sadece metot çağrıları)
Pointcut: Join point'ler arasından "bunlara uygula" diye seçim yapan filtre
Advice: Pointcut ile seçilen metotlarda çalışacak kod
Gerçek Dünya Analojisi
Bir binanın güvenlik kamera sistemi:
Join point = Binadaki her kapı, pencere, koridor (potansiyel izleme noktası)
Pointcut = "Sadece giriş katındaki kapıları izle" kuralı
Advice = Kamera kaydı başlat, hareket algılama yap
execution() — En Temel Designator
execution(), metot imzasına göre eşleşme yapar ve en yaygın kullanılan designator'dır.
Sözdizimi (Syntax)
execution([erişim belirleyici] [dönüş tipi] [sınıf yolu].[metot adı]([parametreler]) [throws])execution(modifiers? return-type declaring-type?.method-name(parameters) throws?)Soru işareti (?) olan kısımlar opsiyoneldir.
Wildcard'lar
| Sembol | Anlamı | Örnek |
|---|---|---|
* | Herhangi bir şey (tek seviye) | * = herhangi bir dönüş tipi, get* = get ile başlayan |
.. | Herhangi sayıda şey | (..) = sıfır veya daha fazla parametre, com.example.. = alt paketler dahil |
+ | Bu tip ve alt tipleri | UserService+ = UserService ve extends/implements eden tüm sınıflar |
Örnek Katalog — Basit'ten Karmaşık'a
// ── Tüm Metotlar ──
// Herhangi bir dönüş tipli, herhangi bir sınıftaki, herhangi parametreli tüm metotlar
@Before("execution(* *(..))")
// ⚠️ ÇOK GENİŞ — toString(), hashCode() dahil her şeyi yakalar!
// ── Dönüş Tipine Göre ──
// void dönen tüm metotlar
@Before("execution(void *(..))")
// String dönen tüm metotlar
@Before("execution(String *(..))")
// List dönen tüm metotlar (generic tip dahil)
@Before("execution(java.util.List *(..))")
// ── Metot İsmine Göre ──
// "get" ile başlayan tüm metotlar
@Before("execution(* get*(..))")
// "find" ile başlayan tüm metotlar
@Before("execution(* find*(..))")
// Tam isim eşleştirme
@Before("execution(* createOrder(..))")
// ── Paket ve Sınıfa Göre ──
// com.example.service paketindeki tüm sınıfların tüm metotları
@Before("execution(* com.example.service.*.*(..))")
// ^^^^^^^^^^^^^^^^^ paket
// ^ herhangi sınıf
// ^ herhangi metot
// ^^ herhangi parametre
// com.example.service ve ALT PAKETLERİNDEKİ tüm metotlar
@Before("execution(* com.example.service..*.*(..))")
// ^^ alt paketler dahil (.. = recursive)
// Belirli bir sınıfın tüm metotları
@Before("execution(* com.example.service.UserService.*(..))")
// Belirli bir metot
@Before("execution(* com.example.service.UserService.findById(..))")
// ── Erişim Belirleyiciye Göre ──
// Sadece public metotlar
@Before("execution(public * *(..))")
// Public ve protected metotlar (Spring AOP'de sadece public çalışır!)
// ⚠️ Spring AOP proxy-based olduğu için private/protected zaten çalışmaz
// ── Parametrelere Göre ──
// Parametre almayan metotlar
@Before("execution(* com.example.service.*.*())")
// ^ boş parantez = parametre yok
// Tek parametre alan metotlar (herhangi tipte)
@Before("execution(* com.example.service.*.*(*))")
// ^ tek parametre
// Tam olarak Long parametre alan metotlar
@Before("execution(* com.example.service.*.*(Long))")
// İlk parametresi String, sonrası herhangi olan metotlar
@Before("execution(* com.example.service.*.*(String, ..))")
// İki parametreli metotlar (herhangi tipte)
@Before("execution(* com.example.service.*.*(*, *))")
// String ve Long parametre alan metotlar (sıra önemli)
@Before("execution(* com.example.service.*.*(String, Long))")
// ── Kombinasyon ──
// public, String dönen, get ile başlayan, service paketindeki metotlar
@Before("execution(public String com.example.service.*.get*(..))")En Çok Kullanılan Pattern'ler
// 1. Service katmanındaki tüm metotlar (en yaygın)
"execution(* com.example.service.*.*(..))"
// 2. Service + alt paketler (modüler proje)
"execution(* com.example.service..*.*(..))"
// 3. Tüm Repository metotları
"execution(* com.example.repository.*.*(..))"
// 4. Tüm Controller metotları
"execution(* com.example.controller.*.*(..))"
// 5. Belirli bir sınıfın tüm metotları
"execution(* com.example.service.OrderService.*(..))"within() — Tip (Sınıf/Paket) Bazlı
within(), sınıf veya paket bazlı eşleşme yapar. Metot imzasına bakmaz — "bu sınıfın/paketin içindeki tüm metotlar" der:
// UserService sınıfındaki TÜM metotlar
@Pointcut("within(com.example.service.UserService)")
// service paketindeki TÜM sınıfların TÜM metotları
@Pointcut("within(com.example.service.*)")
// service ve alt paketlerindeki TÜM sınıflar
@Pointcut("within(com.example.service..*)")execution() vs within() Farkı
// execution() — metot seviyesinde kontrol
"execution(* com.example.service.UserService.find*(..))"
// Sadece "find" ile başlayan metotlar
// within() — sınıf seviyesinde kontrol
"within(com.example.service.UserService)"
// UserService'in TÜM metotları (find, create, delete...)within(), execution()'dan daha kaba bir filtredir. Belirli metotları hedeflemek için execution() kullanın.
@annotation() — Annotation Bazlı
@annotation(), belirli bir annotation ile işaretlenmiş metotları hedefler:
// @Loggable annotation'ına sahip tüm metotlar
@Pointcut("@annotation(com.example.annotation.Loggable)")
// @Transactional metotlar
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
// @Cacheable metotlar
@Pointcut("@annotation(org.springframework.cache.annotation.Cacheable)")Annotation Parametresine Erişim
Pointcut'ta parametre binding kullanarak annotation'a erişebilirsiniz:
// Full qualified name ile
@Around("@annotation(com.example.annotation.Monitored)")
public Object monitor(ProceedingJoinPoint jp) throws Throwable { ... }
// Parametre binding ile (annotation nesnesine erişim)
@Around("@annotation(monitored)") // küçük harf = metot parametre adı
public Object monitor(ProceedingJoinPoint jp, Monitored monitored) throws Throwable {
long threshold = monitored.warnThresholdMs(); // Annotation parametresine erişim
// ...
}@within() — Sınıf Annotation'ına Göre
@within(), belirli bir annotation ile işaretlenmiş sınıfların tüm metotlarını hedefler:
// @Service annotation'ına sahip sınıfların tüm metotları
@Pointcut("@within(org.springframework.stereotype.Service)")
// @RestController sınıflarının tüm metotları
@Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
// Custom annotation ile
@Pointcut("@within(com.example.annotation.Auditable)")@annotation() vs @within() Farkı
// @annotation → METOT seviyesinde annotation arar
@Service
public class UserService {
@Loggable // ← Bu metot hedeflenir
public void createUser() { }
public void deleteUser() { } // ← Bu hedeflenmez
}
// @within → SINIF seviyesinde annotation arar
@Loggable // ← Sınıfa konmuş
@Service
public class UserService {
public void createUser() { } // ← HER İKİSİ de hedeflenir
public void deleteUser() { } // ← HER İKİSİ de hedeflenir
}args() — Parametre Tipine Göre
// String parametre alan tüm metotlar
@Pointcut("args(String)")
// İlk parametresi Long olan metotlar
@Pointcut("args(Long, ..)")
// İki String parametre alan metotlar
@Pointcut("args(String, String)")args() ile Parametre Erişimi
@Before("execution(* com.example.service.*.*(..)) && args(id, ..)")
public void logIdParameter(JoinPoint jp, Long id) {
log.info("Metot {} çağrıldı, ID: {}", jp.getSignature().getName(), id);
}target() ve this() — Nesne Tipine Göre
// target() — gerçek nesnenin tipi
@Pointcut("target(com.example.service.UserService)")
// this() — proxy nesnesinin tipi
@Pointcut("this(com.example.service.UserService)")Fark: CGLIB proxy'de target ve this genellikle aynı tipi döner. JDK proxy'de this sadece interface tipini döner.
target() ile Polimorfik Eşleşme
// BaseService'i implement eden TÜM bean'ler
@Pointcut("target(com.example.service.BaseService)")
// UserService, OrderService, ProductService — hepsi eşleşir (BaseService'i extend ediyorlarsa)Bu özellikle bir interface veya abstract class'ı implement eden tüm service'lere genel bir advice uygulamak istediğinizde faydalıdır.
bean() — Spring Bean İsmine Göre
Spring AOP'ye özel bir designator — AspectJ'de yoktur:
// İsmi "userService" olan bean
@Pointcut("bean(userService)")
// İsmi "Service" ile biten tüm bean'ler
@Pointcut("bean(*Service)")
// İsmi "order" ile başlayan bean'ler
@Pointcut("bean(order*)")Pointcut Birleştirme — &&, ||, !
Birden fazla pointcut'ı mantıksal operatörlerle birleştirebilirsiniz:
@Aspect
@Component
public class CombinedAspect {
// ── AND (&&) — her iki koşul da sağlanmalı ──
// Service paketinde VE public metotlar
@Before("execution(* com.example.service.*.*(..)) && execution(public * *(..))")
public void publicServiceMethods(JoinPoint jp) { }
// ── OR (||) — herhangi biri yeterli ──
// Service VEYA Controller metotları
@Before("execution(* com.example.service.*.*(..)) || " +
"execution(* com.example.controller.*.*(..))")
public void serviceOrControllerMethods(JoinPoint jp) { }
// ── NOT (!) — hariç tut ──
// Service metotları AMA get ile başlayanlar HARİÇ
@Before("execution(* com.example.service.*.*(..)) && " +
"!execution(* *.get*(..))")
public void nonGetServiceMethods(JoinPoint jp) { }
// ── Karmaşık kombinasyon ──
// Service'teki @Transactional metotlar ama delete* hariç
@Before("within(com.example.service.*) && " +
"@annotation(org.springframework.transaction.annotation.Transactional) && " +
"!execution(* *.delete*(..))")
public void transactionalNonDeleteServiceMethods(JoinPoint jp) { }
}Named Pointcut — @Pointcut ile Yeniden Kullanım
Aynı pointcut ifadesini her yerde tekrarlamamak için named pointcut tanımlayın:
@Aspect
@Component
@Slf4j
public class ApplicationAspect {
// ── Named Pointcut Tanımları (gövdesi boş metotlar) ──
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Pointcut("execution(* com.example.controller.*.*(..))")
public void controllerMethods() {}
@Pointcut("execution(* com.example.repository.*.*(..))")
public void repositoryMethods() {}
@Pointcut("@annotation(com.example.annotation.Loggable)")
public void loggableMethods() {}
// ── Bileşik Pointcut'lar ──
@Pointcut("serviceMethods() || controllerMethods()")
public void applicationMethods() {}
@Pointcut("serviceMethods() && !execution(* *.get*(..))")
public void mutatingServiceMethods() {}
// ── Advice'larda Kullanım ──
@Before("serviceMethods()")
public void logServiceEntry(JoinPoint jp) {
log.info("→ Service: {}", jp.getSignature().toShortString());
}
@Before("controllerMethods()")
public void logControllerEntry(JoinPoint jp) {
log.info("→ Controller: {}", jp.getSignature().toShortString());
}
@AfterThrowing(pointcut = "applicationMethods()", throwing = "ex")
public void logError(JoinPoint jp, Exception ex) {
log.error("✕ {}: {}", jp.getSignature().toShortString(), ex.getMessage());
}
}Farklı Aspect'lerden Referans
Named pointcut'lar başka aspect'lerden de referans edilebilir:
// Pointcut tanımları ayrı bir sınıfta
public class CommonPointcuts {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}
@Pointcut("execution(* com.example.controller.*.*(..))")
public void controllerMethods() {}
}
// Başka aspect'te kullanım
@Aspect @Component
public class LoggingAspect {
@Before("com.example.aspect.CommonPointcuts.serviceMethods()")
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ tam yol gerekli
public void logService(JoinPoint jp) { ... }
}Designator Seçim Rehberi
| İhtiyaç | Designator | Örnek |
|---|---|---|
| Belirli metot imzası | execution() | Paket, sınıf, metot adı, parametreler |
| Tüm sınıf metotları | within() | Paket veya sınıf bazlı |
| Annotation'lı metotlar | @annotation() | @Loggable, @Timed |
| Annotation'lı sınıflar | @within() | @Service, @Controller |
| Bean ismine göre | bean() | *Service, order* |
| Parametre tipine göre | args() | Long, String, .. |
En İyi Pratik Kombinasyonları
// Production'da en sık kullanılan pattern'ler:
// 1. Service katmanı loglama
"execution(* com.example.service.*.*(..))"
// 2. Custom annotation ile selektif
"@annotation(com.example.annotation.Monitored)"
// 3. Controller + Service (genel uygulama katmanı)
"within(com.example.service..*) || within(com.example.controller..*)"
// 4. Tüm write operasyonları (CUD — Create, Update, Delete)
"execution(* com.example.service.*.create*(..)) || " +
"execution(* com.example.service.*.update*(..)) || " +
"execution(* com.example.service.*.delete*(..))"
// 5. Bean ismine göre (Spring-specific)
"bean(*Service) || bean(*Controller)"Performans ve Debug İpuçları
Pointcut Eşleşme Performansı
Spring AOP, uygulama başlangıcında tüm bean'lerin metotlarını pointcut expression'lara karşı kontrol eder. Çok karmaşık veya çok sayıda pointcut, başlangıç süresini uzatabilir:
// ❌ Yavaş başlangıç — çok geniş ve karmaşık
@Before("execution(* *(..)) && !within(org.springframework..*) && " +
"!within(java..*) && !within(javax..*)")
// Tüm metotları tarar, sonra hariç tutar
// ✅ Hızlı başlangıç — daraltılmış
@Before("execution(* com.example.service.*.*(..))")
// Sadece service paketini tararDebug: Hangi Metotlar Eşleşiyor?
Pointcut'ınızın beklediğiniz metotları yakalayıp yakalamadığını test etmek için:
@Aspect
@Component
@Slf4j
public class PointcutDebugger {
// Geçici debug advice'ı — production'a göndermeyin!
@Before("execution(* com.example.service.*.*(..))")
public void debugPointcut(JoinPoint jp) {
log.info("🎯 Pointcut eşleşti: {} — {}",
jp.getTarget().getClass().getSimpleName(),
jp.getSignature().toLongString());
}
}Spring Boot Actuator kullanıyorsanız, AOP proxy bilgisini bean endpoint'inden görebilirsiniz:
# Belirli bir bean'in proxy olup olmadığını kontrol
curl http://localhost:8080/actuator/beans | jq '.contexts[].beans.userService'Yaygın Hatalar
1. ❌ Alt Paketleri Unutmak
// ❌ Sadece service paketini kapsar, alt paketleri KAPSAMAZ
"execution(* com.example.service.*.*(..))"
// com.example.service.UserService → ✅
// com.example.service.user.UserService → ❌
// ✅ Alt paketler dahil
"execution(* com.example.service..*.*(..))"
// ^^ iki nokta = recursive2. ❌ Çok Geniş Pointcut
// ❌ ÇOK GENİŞ — toString, hashCode, equals hepsi yakalanır
"execution(* *(..))"
// ❌ GENİŞ — repository metotları da yakalanır (N+1 sorunu tetikleyebilir)
"execution(* com.example..*.*(..))"
// ✅ Hedefli
"execution(* com.example.service.*.*(..))"3. ❌ Paket Yolunu Yanlış Yazmak
// ❌ Compile-time hata yok, ama hiçbir metot eşleşmez!
"execution(* com.exmple.service.*.*(..))"
// ^^^^^^ typo!
// İpucu: Advice hiç çalışmıyorsa, pointcut ifadesini kontrol edin4. ❌ within() ile Metot Filtrelemeye Çalışmak
// ❌ within() metot filtreleyemez
"within(com.example.service.UserService.findById)"
// Bu bir sınıf adı değil! Hata verir.
// ✅ Metot filtreleme için execution()
"execution(* com.example.service.UserService.findById(..))"Özet
Pointcut expression, advice'ın hangi metotlara uygulanacağını belirleyen filtredir
`execution()` en yaygın designator — metot imzasına göre eşleşir:
execution(dönüş paket.Sınıf.metot(param))`within()` sınıf/paket bazlı, `@annotation()` annotation bazlı, `bean()` Spring bean ismine göre eşleşir
Wildcard'lar:
*= tek seviye,..= çoklu seviye (parametreler veya paketler),+= alt tipler`&&`, `||`, `!` operatörleriyle pointcut'lar birleştirilebilir
Named pointcut (
@Pointcut) ile yeniden kullanılabilir, okunabilir ifadeler oluşturunİki nokta (
..) → alt paketler dahil; tek yıldız (*) → sadece mevcut paketPointcut'ı mümkün olduğunca dar tutun — gereksiz geniş eşleşme performans ve debug sorunlarına yol açar
`bean()` Spring AOP'ye özel bir designator — bean ismi veya pattern ile eşleşme sağlar
Debug için geçici
@Beforeadvice ekleyerek hangi metotların eşleştiğini görebilirsinizKarmaşık pointcut ifadelerini named pointcut ile parçalayıp okunabilirliği artırın
AI Asistan
Sorularını yanıtlamaya hazır