← Kursa Dön
📄 Text · 30 min

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

SembolAnlamıÖ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 tipleriUserService+ = 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örebean()*Service, order*
Parametre tipine göreargs()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 tarar

Debug: 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 = recursive

2. ❌ Ç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 edin

4. ❌ 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 paket

  • Pointcut'ı 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 @Before advice ekleyerek hangi metotların eşleştiğini görebilirsiniz

  • Karmaşık pointcut ifadelerini named pointcut ile parçalayıp okunabilirliği artırın