Spring AOP
Bu modülde Spring uygulamalarında tekrar eden ortak davranışları business kodun içinden nasıl ayırdığımızı göreceğiz. Logging, audit, transaction, security ve performans ölçümü gibi konuların neden ayrı bir mekanizma istediğini basitten başlayarak anlatacağız; sonra MiniBank üzerinde audit logging davranışını birlikte ekleyeceğiz.
AOP’yi “magic” değil, proxy üzerinden çalışan kontrollü bir mekanizma olarak okuyacaksın.
Ortak davranışları ayırma
Logging, audit, transaction ve security gibi kodların neden business metodların içine dağılmaması gerektiğini anlayacaksın.
Proxy mental modeli
Spring AOP’nin çağrıyı gerçek bean’e göndermeden önce bir proxy üzerinden yakaladığını göreceksin.
MiniBank audit altyapısı
MiniBank operasyonlarına @Auditable annotation’ı ile merkezi audit logging davranışı ekleyeceksin.
Kavramları önce basit karşılıklarıyla oturtalım.
AOP konusu ilk bakışta soyut gelebilir. Bu yüzden önce kavramları günlük yazılım geliştirme diliyle netleştirelim; sonra Spring’in içeride nasıl çalıştırdığına geçelim.
AOP
Aspect Oriented Programming, ortak davranışları business koddan ayırma yaklaşımıdır. Spring’de özellikle proxy mekanizmasıyla karşımıza çıkar.
Cross-cutting concern
Birden fazla katmanda tekrar eden ortak ihtiyaçtır. Logging, audit, security ve transaction bunun en yaygın örnekleridir.
Aspect
Ortak davranışı taşıyan sınıftır. Örneğin AuditAspect, audit davranışının merkezi olduğu yerdir.
Advice
Aspect’in çalıştırdığı aksiyondur. Methoddan önce, sonra veya methodun etrafında çalışabilir.
Pointcut
Advice’ın nerede çalışacağını belirleyen ifadedir. “Şu paketteki şu methodlar çalışınca araya gir” demenin yoludur.
Join Point
Aspect’in araya girebileceği noktadır. Spring AOP’de pratikte en önemli join point method çalıştırmadır.
Proxy
Gerçek bean’in önünde duran temsilci objedir. Çağrıyı yakalar, ek davranışı çalıştırır, sonra gerçek methoda gider.
Target Object
Gerçek business logic’in bulunduğu asıl bean’dir. Proxy, sonunda çağrıyı bu objeye iletir.
Self-invocation
Bir bean’in kendi içindeki başka methodu this.method() gibi çağırmasıdır. Bu çağrı proxy’den geçmediği için AOP davranışı beklenmeyebilir.
Basitten başlayıp proxy mantığına kadar gidelim.
3.1 AOP nedir?
AOP, business kodun içine tekrar tekrar yazmak istemediğimiz ortak davranışları ayrı bir yerde toplama yaklaşımıdır.
Bir bankacılık uygulaması düşünelim. Para transferi yapılırken log atmak isteriz. Aynı işlemde audit kaydı tutmak isteriz. Belki yetki kontrolü yapmak isteriz. Belki methodun ne kadar sürdüğünü ölçmek isteriz. Bu ihtiyaçlar sadece transfer metoduna özel değildir; hesap açma, müşteri güncelleme, işlem geçmişi görüntüleme gibi birçok yerde tekrar eder.
Eğer bu ortak işleri her methodun içine tek tek yazarsak business kodun amacı bulanıklaşır. transfer() metodu sadece para transferi yapıyor gibi görünmez; log, audit, güvenlik, performans ölçümü, hata formatlama gibi yan işler de aynı methodun içine doluşur. AOP burada devreye girer ve şunu söyler: “Bu ortak davranışı ayrı bir yerde tanımlayalım, hangi methodlarda çalışacağını da kural olarak belirtelim.”
AOP, business methodun etrafına görünmez ama kontrollü bir katman koymak gibidir. Method çağrılır; önce ortak davranış çalışır, sonra gerçek business logic çalışır, en sonda gerekiyorsa tekrar ortak davranış çalışır.
3.2 Aspect, Advice, Pointcut ve Join Point kavramları
AOP öğrenirken en çok kafa karıştıran şey kavramların isimleri olabilir. Aslında hepsi aynı cümleyi farklı parçalara böler: “Şu methodlar çalışırken, şu ortak davranışı çalıştır.”
Aspect = davranışın evi
Audit loglama davranışını AuditAspect içinde toplarız. Böylece audit ile ilgili kod AccountService, CustomerService veya TransferService içine dağılmaz.
Advice = yapılacak iş
Methoddan önce log yaz, methoddan sonra süreyi ölç, hata olursa audit kaydı oluştur gibi aksiyonlardır.
Pointcut = nerede çalışacak?
@Auditable annotation’ı olan methodlarda çalış veya belirli paketteki service methodlarında çalış gibi kuraldır.
Join Point = yakalanan nokta
Spring AOP için en pratik join point method execution’dır. Yani method çağrısı sırasında araya gireriz.
@Aspect
@Component
public class AuditAspect {
@Around("@annotation(auditable)") // Pointcut: @Auditable olan methodlar
public Object audit(ProceedingJoinPoint joinPoint,
Auditable auditable) throws Throwable {
// Advice: methodun etrafında çalışacak davranış
long start = System.currentTimeMillis();
try {
Object result = joinPoint.proceed(); // Join point: gerçek method çalışır
long duration = System.currentTimeMillis() - start;
// Audit log burada oluşturulur
return result;
} catch (Throwable ex) {
// Hata audit'i burada oluşturulur
throw ex;
}
}
}
3.3 Spring AOP proxy mantığı
Spring AOP’yi anlamanın en kritik noktası şudur: Spring çoğu durumda gerçek bean’i doğrudan çağırmaz; onun önüne bir proxy koyar.
Proxy’yi bir kapı görevlisi gibi düşünebiliriz. Dışarıdan gelen çağrı önce proxy’ye gelir. Proxy “bu method için aspect çalışmalı mı?” diye bakar. Evetse önce advice’ı çalıştırır, sonra gerçek bean’e geçer. İş bitince dönüşte de tekrar araya girebilir.
Bu mekanizma sayesinde AccountOperationService içinde audit kodu yazmadan, o method çağrıldığında audit log oluşturabiliriz. Business kod sade kalır; audit davranışı merkezi bir aspect içinde yönetilir.
AOP olmadan
Her methodun içine manuel log/audit kodu yazılır. Zamanla business logic ile teknik işler karışır.
AOP ile
Business method sadece işini yapar. Audit/logging davranışı aspect içinde merkezi olarak çalışır.
JDK Dynamic Proxy vs CGLIB
Spring AOP proxy üretirken genellikle iki yol kullanır. Eğer bean bir interface üzerinden proxylenecekse JDK Dynamic Proxy kullanılabilir. Interface yoksa veya class tabanlı proxy gerekiyorsa CGLIB devreye girer. Eğitim sırasında bu ayrımı ezberletmekten çok şunu oturtacağız: hangi yöntem kullanılırsa kullanılsın, çağrının proxy üzerinden geçmesi gerekir.
JDK Dynamic Proxy
Interface tabanlı proxy üretir. Çağrı interface üzerinden temsil edilir.
CGLIB
Class tabanlı proxy üretir. Spring Boot uygulamalarında class proxy davranışıyla sık karşılaşılır.
Proxy mantığını bilmeden AOP kullanmak kolaydır; ama bir gün aspect’in neden çalışmadığını anlamak zorlaşır. “Çağrı proxy’den geçti mi?” sorusu AOP debug ederken sorulacak ilk sorudur.
3.4 Advice türleri: @Before, @After, @Around
Advice, aspect’in method çağrısı sırasında ne yapacağını belirler. Her advice tipi farklı bir zamanlama için uygundur. Eğitimde özellikle @Around üzerinde duracağız, çünkü hem methoddan önce hem sonra çalışabilir, süre ölçebilir, hata yakalayabilir ve sonucu kontrol edebilir.
@Before
Method çalışmadan önce devreye girer. Basit giriş logu veya yetki ön kontrolü gibi işler için uygundur.
@After / @AfterReturning
Method tamamlandıktan sonra çalışır. Sonuç başarılıysa ayrı, her durumda ayrı davranış yazılabilir.
@Around
Methodun etrafını sarar. Süre ölçümü, audit, hata loglama ve sonucu dönmeden önce işleme için en esnek yapıdır.
@Around("@annotation(auditable)")
public Object measureAndAudit(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - start;
log.info("operation succeeded method={} duration={}ms",
joinPoint.getSignature().getName(), duration);
return result;
} catch (Throwable ex) {
log.warn("operation failed method={} error={}",
joinPoint.getSignature().getName(), ex.getMessage());
throw ex;
}
}
3.5 @Transactional aslında AOP ile neden ilişkilidir?
@Transactional çoğu geliştirici için sadece annotation gibi görünür; ama arkasında proxy üzerinden çalışan bir davranış vardır.
Bir service methoduna @Transactional koyduğumuzda Spring o method çağrısını yakalayabilir. Methoda girmeden önce transaction başlatır, method başarılı biterse commit eder, exception fırlarsa rollback kararı verir. Bu davranışın business methodun içine yazılmaması büyük avantajdır.
Buradaki kritik nokta yine proxy’dir. Transaction davranışı methodun içine compile-time’da yapıştırılmaz. Runtime’da Spring proxy çağrıyı yakalar ve transaction yönetimini methodun etrafına sarar. Bu yüzden @Transactional çalışmadığında ilk kontrol edilecek konulardan biri method çağrısının proxy üzerinden geçip geçmediğidir.
3.6 Self-invocation problemi
Self-invocation, AOP’nin en önemli senior tuzaklarından biridir: aynı class içinden yapılan method çağrısı çoğu durumda proxy’ye uğramaz.
Diyelim ki TransferService içinde public bir method başka bir @Transactional methodu aynı class içinden çağırıyor. Kod okunurken “annotation var, transaction çalışır” diye düşünebiliriz. Ama çağrı this.innerMethod() gibi aynı obje üzerinde yapılıyorsa Spring proxy araya giremez. Çünkü dışarıdan proxy’ye gelen bir çağrı yoktur; obje kendi iç methodunu doğrudan çağırmıştır.
@Service
public class TransferService {
public void transfer() {
validate();
saveAuditInNewTransaction(); // aynı class içinden çağrı
}
@Transactional
public void saveAuditInNewTransaction() {
// Transaction beklenir ama çağrı proxy'den geçmezse çalışmayabilir.
}
}
@Service
@RequiredArgsConstructor
public class TransferService {
private final AuditService auditService;
public void transfer() {
validate();
auditService.saveAudit(); // başka bean üzerinden proxy'ye gider
}
}
@Service
public class AuditService {
@Transactional
public void saveAudit() {
// Transaction proxy üzerinden uygulanır.
}
}
AOP davranışı beklediğin yerde çalışmıyorsa sadece annotation’a bakma; çağrının proxy üzerinden geçip geçmediğine bak.
MiniBank Case 03 — Audit Logging with AOP
Bu case’de MiniBank operasyonlarına merkezi audit logging davranışı ekleyeceğiz. Case bir hata avı değildir; eğitmen doğru yapıyı kod üzerinde gösterecek, sonra katılımcılar aynı pattern’i customer operasyonları için uygulayacak. Görev listesi, doğrulama komutları, kod adımları ve kabul kriterleri Lab Dashboard'da.
Lab sırasında geride kalırsan lab-03-complete branch'ine geçip herkesle aynı checkpoint'ten devam edebilirsin.
Bu modülden akılda kalması gerekenler
Logging, audit, transaction ve security gibi davranışlar business methodların içine dağılmadan yönetilebilir.
Çağrı proxy’den geçerse advice çalışır; proxy bypass edilirse beklenen AOP davranışı çalışmayabilir.
Methoddan önce ve sonra çalışabilir, süre ölçebilir, sonucu yakalayabilir ve hata durumunu loglayabilir.
Transaction boundary çoğu senaryoda method çağrısının proxy üzerinden yakalanmasıyla uygulanır.
Aynı class içinden yapılan çağrı proxy’ye uğramayabilir. Bu yüzden servis sınırları doğru tasarlanmalıdır.
5 kısa kontrol sorusu
1. AOP hangi problemi çözmek için kullanılır?
AOP, birçok method veya katmanda tekrar eden logging, audit, transaction, security gibi ortak davranışları business koddan ayırmak için kullanılır.
2. Aspect, Advice ve Pointcut arasındaki fark nedir?
Aspect ortak davranışın bulunduğu sınıftır. Advice çalışacak aksiyondur. Pointcut ise advice’ın hangi methodlarda çalışacağını belirleyen kuraldır.
3. Spring AOP’de proxy neden önemlidir?
Çünkü Spring AOP çoğunlukla method çağrısını proxy üzerinden yakalar. Çağrı proxy’den geçmezse advice çalışmayabilir.
4. @Around advice ne avantaj sağlar?
Methoddan önce ve sonra çalışabildiği için süre ölçümü, başarılı/başarısız audit, hata yakalama ve sonuç loglama gibi ihtiyaçları tek yerde karşılayabilir.
5. Self-invocation neden risklidir?
Aynı bean içinde bir methodun başka bir methodu doğrudan çağırması proxy’yi bypass edebilir. Bu durumda AOP, @Transactional veya @Async gibi davranışlar beklenen şekilde çalışmayabilir.