@Around Advice ile Logging ve Performance
Bir arabanın dashboard'unu düşünün. Motor çalışırken hız, devir, yakıt, motor sıcaklığı — hepsini gerçek zamanlı izlersiniz. Ama bu sensörler motorun içine gömülü değildir. Motor sadece işini yapar; sensörler dışarıdan sararak (around) ölçüm alır. Motor bozulursa sensör alarm verir, motor yavaşsa uyarır — ama motorun çalışma mantığını hiç değiştirmez.
Spring AOP'deki @Around advice, tam olarak bu sensör sistemidir. Bir metodu tamamen sarar — çalışmadan önce, çalıştıktan sonra ve hata durumunda müdahale edebilir. Hatta metodu hiç çalıştırmama seçeneğiniz bile vardır (circuit breaker gibi). Bu, @Before + @AfterReturning + @AfterThrowing'in hepsini tek bir yerde birleştirmektir.
Bu derste @Around advice'ın gerçek dünya kullanımlarını detaylıca inceleyeceğiz: performance monitoring, structured logging, caching, retry mekanizması ve rate limiting.
@Around Neden En Güçlü Advice?
Diğer advice türleri ile karşılaştırma:
| Özellik | @Before | @After | @AfterReturning | @AfterThrowing | @Around |
|---|---|---|---|---|---|
| Metot öncesi kod | ✅ | ❌ | ❌ | ❌ | ✅ |
| Metot sonrası kod | ❌ | ✅ | ✅ | ✅ | ✅ |
| Dönüş değerine erişim | ❌ | ❌ | ✅ | ❌ | ✅ |
| Exception'a erişim | ❌ | ❌ | ❌ | ✅ | ✅ |
| Dönüş değerini değiştirme | ❌ | ❌ | ❌ | ❌ | ✅ |
| Metodu çağırmama | ❌ | ❌ | ❌ | ❌ | ✅ |
| Parametreleri değiştirme | ❌ | ❌ | ❌ | ❌ | ✅ |
| Süre ölçümü | ❌ | ❌ | ❌ | ❌ | ✅ |
@Around tek başına diğer dördünün yapabildiği her şeyi yapabilir. Peki neden hep @Around kullanmıyoruz? Çünkü en az yetki prensibi (principle of least power) geçerlidir — sadece loglama için @Before yeterliyse, @Around kullanmak gereksiz karmaşıklıktır.
ProceedingJoinPoint — Kontrol Paneli
@Around advice'ın diğerlerinden farkı ProceedingJoinPoint parametresidir. Normal JoinPoint sadece bilgi verir, ProceedingJoinPoint ise kontrol sağlar:
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// ─── BİLGİ ALMA (JoinPoint'ten miras) ───
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
// ─── KONTROL (ProceedingJoinPoint'e özel) ───
// Hedef metodu orijinal parametrelerle çağır
Object result = joinPoint.proceed();
// VEYA parametreleri değiştirerek çağır
Object[] modifiedArgs = new Object[]{sanitize(args[0])};
Object result2 = joinPoint.proceed(modifiedArgs);
// VEYA hiç çağırma (circuit breaker)
// return cachedValue;
return result; // ⚠️ Sonucu döndürmeyi UNUTMA!
}⚠️ Kritik Kural: @Around advice'ta joinPoint.proceed() çağrısının sonucunu mutlaka return etmelisiniz. Aksi halde hedef metot çalışsa bile çağırana null döner:
// ❌ YANLIŞ — sonuç kaybolur
@Around("execution(* com.example.service.*.*(..))")
public Object broken(ProceedingJoinPoint jp) throws Throwable {
jp.proceed(); // Sonuç atanmadı!
// return yok → null döner → NullPointerException
}
// ✅ DOĞRU
@Around("execution(* com.example.service.*.*(..))")
public Object correct(ProceedingJoinPoint jp) throws Throwable {
Object result = jp.proceed();
return result; // Sonucu çağırana ilet
}Kullanım 1: Performance Monitoring
Production'daki en önemli cross-cutting concern'lerden biri, metot çalışma sürelerini izlemektir. Yavaş metotlar tespit edilmezse zamanla kullanıcı deneyimi kötüleşir.
Temel Performance Aspect
@Aspect
@Component
@Slf4j
public class PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
String methodSignature = joinPoint.getSignature().toShortString();
long startTime = System.nanoTime();
try {
Object result = joinPoint.proceed();
long elapsedMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
log.info("✅ {} completed in {}ms", methodSignature, elapsedMs);
// Yavaş metot uyarısı
if (elapsedMs > 1000) {
log.warn("🐢 SLOW METHOD: {} took {}ms (threshold: 1000ms)",
methodSignature, elapsedMs);
}
return result;
} catch (Throwable ex) {
long elapsedMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
log.error("❌ {} failed after {}ms: {}", methodSignature, elapsedMs,
ex.getMessage());
throw ex;
}
}
}💡 İpucu: System.nanoTime() kullanın, System.currentTimeMillis() değil. currentTimeMillis() duvar saati zamanıdır ve NTP senkronizasyonu sırasında geriye sıçrayabilir. nanoTime() monotonik zamandır — sadece süre ölçümü için tasarlanmıştır.
Custom Annotation ile Selektif Monitoring
Her metodu izlemek gereksiz log kirliliği yaratır. Bunun yerine sadece izlemek istediğiniz metotları annotation ile işaretleyin:
// ─── Annotation Tanımı ───
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Monitored {
String value() default ""; // Özel etiket
long warnThresholdMs() default 500; // Uyarı eşiği (ms)
long errorThresholdMs() default 3000; // Hata eşiği (ms)
}
// ─── Aspect ───
@Aspect
@Component
@Slf4j
public class MonitoringAspect {
@Around("@annotation(monitored)")
public Object monitor(ProceedingJoinPoint joinPoint, Monitored monitored) throws Throwable {
String label = monitored.value().isEmpty()
? joinPoint.getSignature().toShortString()
: monitored.value();
long start = System.nanoTime();
try {
Object result = joinPoint.proceed();
long elapsedMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
// Eşik seviyelerine göre farklı log level
if (elapsedMs >= monitored.errorThresholdMs()) {
log.error("🔴 {} took {}ms (ERROR threshold: {}ms)",
label, elapsedMs, monitored.errorThresholdMs());
} else if (elapsedMs >= monitored.warnThresholdMs()) {
log.warn("🟡 {} took {}ms (WARN threshold: {}ms)",
label, elapsedMs, monitored.warnThresholdMs());
} else {
log.debug("🟢 {} completed in {}ms", label, elapsedMs);
}
return result;
} catch (Throwable ex) {
long elapsedMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
log.error("💥 {} failed after {}ms: {}", label, elapsedMs, ex.getMessage());
throw ex;
}
}
}
// ─── Kullanım ───
@Service
public class OrderService {
@Monitored(value = "Create Order", warnThresholdMs = 200, errorThresholdMs = 1000)
public OrderResponse createOrder(CreateOrderRequest request) {
// İş mantığı...
}
@Monitored("Fetch User Orders")
public List<OrderResponse> getUserOrders(Long userId) {
// İş mantığı...
}
}Micrometer Entegrasyonu
Production'da loglardan daha etkili bir yol, metrics toplamaktır. Micrometer ile metot sürelerini Prometheus/Grafana'ya aktarabilirsiniz:
@Aspect
@Component
public class MetricsAspect {
private final MeterRegistry meterRegistry;
public MetricsAspect(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Around("@annotation(monitored)")
public Object recordMetrics(ProceedingJoinPoint joinPoint,
Monitored monitored) throws Throwable {
String metricName = monitored.value().isEmpty()
? joinPoint.getSignature().getName()
: monitored.value().replace(" ", "_").toLowerCase();
Timer.Sample sample = Timer.start(meterRegistry);
try {
Object result = joinPoint.proceed();
sample.stop(Timer.builder("method.execution")
.tag("method", metricName)
.tag("outcome", "success")
.register(meterRegistry));
return result;
} catch (Throwable ex) {
sample.stop(Timer.builder("method.execution")
.tag("method", metricName)
.tag("outcome", "error")
.tag("exception", ex.getClass().getSimpleName())
.register(meterRegistry));
meterRegistry.counter("method.errors",
"method", metricName,
"exception", ex.getClass().getSimpleName()
).increment();
throw ex;
}
}
}Kullanım 2: Structured Logging
Modern uygulamalarda log mesajlarının yapılandırılmış (structured) olması önemlidir. JSON formatında loglar, ELK Stack / Grafana Loki gibi araçlarla kolayca aranabilir:
@Aspect
@Component
@Slf4j
public class StructuredLoggingAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object logWithContext(ProceedingJoinPoint joinPoint) throws Throwable {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
String fullMethod = className + "." + methodName;
// MDC'ye bağlam bilgisi ekle (her log satırında görünür)
MDC.put("method", fullMethod);
MDC.put("args", summarizeArgs(joinPoint.getArgs()));
long start = System.nanoTime();
try {
log.info("Method entry");
Object result = joinPoint.proceed();
long elapsedMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
MDC.put("duration_ms", String.valueOf(elapsedMs));
MDC.put("outcome", "success");
log.info("Method exit");
return result;
} catch (Throwable ex) {
long elapsedMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
MDC.put("duration_ms", String.valueOf(elapsedMs));
MDC.put("outcome", "error");
MDC.put("error_type", ex.getClass().getSimpleName());
log.error("Method failed: {}", ex.getMessage());
throw ex;
} finally {
MDC.remove("method");
MDC.remove("args");
MDC.remove("duration_ms");
MDC.remove("outcome");
MDC.remove("error_type");
}
}
private String summarizeArgs(Object[] args) {
if (args == null || args.length == 0) return "[]";
return Arrays.stream(args)
.map(arg -> arg == null ? "null" : arg.getClass().getSimpleName())
.collect(Collectors.joining(", ", "[", "]"));
}
}⚠️ Dikkat: finally bloğunda MDC'yi temizlemeyi asla unutmayın. Thread pool'larda aynı thread farklı istekler için yeniden kullanılır — MDC temizlenmezse önceki isteğin bilgileri sonraki isteğe sızar.
Kullanım 3: Retry Mekanizması
Geçici hatalar (network timeout, database connection lost) için otomatik yeniden deneme:
// ─── Annotation ───
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retryable {
int maxAttempts() default 3;
long initialDelayMs() default 100;
double backoffMultiplier() default 2.0;
Class<? extends Throwable>[] retryOn() default {Exception.class};
Class<? extends Throwable>[] noRetryOn() default {};
}
// ─── Aspect ───
@Aspect
@Component
@Slf4j
public class RetryAspect {
@Around("@annotation(retryable)")
public Object retry(ProceedingJoinPoint joinPoint, Retryable retryable) throws Throwable {
int maxAttempts = retryable.maxAttempts();
long delay = retryable.initialDelayMs();
String methodName = joinPoint.getSignature().toShortString();
Throwable lastException = null;
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
try {
if (attempt > 1) {
log.info("🔄 {} — Retry attempt {}/{}", methodName, attempt, maxAttempts);
}
return joinPoint.proceed();
} catch (Throwable ex) {
lastException = ex;
// Bu exception retry edilmeli mi?
if (!shouldRetry(ex, retryable)) {
log.warn("🚫 {} — Exception {} is not retryable, throwing immediately",
methodName, ex.getClass().getSimpleName());
throw ex;
}
if (attempt < maxAttempts) {
log.warn("⚠️ {} — Attempt {}/{} failed: {}. Retrying in {}ms...",
methodName, attempt, maxAttempts,
ex.getMessage(), delay);
Thread.sleep(delay);
delay = (long) (delay * retryable.backoffMultiplier());
} else {
log.error("💀 {} — All {} attempts failed", methodName, maxAttempts);
}
}
}
throw lastException;
}
private boolean shouldRetry(Throwable ex, Retryable retryable) {
// noRetryOn listesinde mi?
for (Class<? extends Throwable> noRetry : retryable.noRetryOn()) {
if (noRetry.isInstance(ex)) return false;
}
// retryOn listesinde mi?
for (Class<? extends Throwable> retry : retryable.retryOn()) {
if (retry.isInstance(ex)) return true;
}
return false;
}
}
// ─── Kullanım ───
@Service
public class PaymentService {
@Retryable(
maxAttempts = 3,
initialDelayMs = 200,
backoffMultiplier = 2.0,
retryOn = {TransientDataAccessException.class, SocketTimeoutException.class},
noRetryOn = {BusinessRuleException.class}
)
public PaymentResult processPayment(PaymentRequest request) {
return paymentGateway.charge(request);
}
}Log çıktısı:
⚠️ PaymentService.processPayment(..) — Attempt 1/3 failed: Connection timed out. Retrying in 200ms...
🔄 PaymentService.processPayment(..) — Retry attempt 2/3
⚠️ PaymentService.processPayment(..) — Attempt 2/3 failed: Connection timed out. Retrying in 400ms...
🔄 PaymentService.processPayment(..) — Retry attempt 3/3
✅ PaymentService.processPayment(..) completed in 623msKullanım 4: Rate Limiting
Belirli metotların saniyede/dakikada kaç kez çağrılabileceğini sınırlamak:
// ─── Annotation ───
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimited {
int maxRequests() default 10;
int windowSeconds() default 60;
String key() default ""; // Boşsa metot adı kullanılır
}
// ─── Aspect ───
@Aspect
@Component
@Slf4j
public class RateLimitAspect {
// Basit in-memory rate limiter (production'da Redis kullanın)
private final Map<String, Deque<Instant>> requestLog = new ConcurrentHashMap<>();
@Around("@annotation(rateLimited)")
public Object rateLimit(ProceedingJoinPoint joinPoint,
RateLimited rateLimited) throws Throwable {
String key = rateLimited.key().isEmpty()
? joinPoint.getSignature().toShortString()
: rateLimited.key();
int maxRequests = rateLimited.maxRequests();
Duration window = Duration.ofSeconds(rateLimited.windowSeconds());
Deque<Instant> timestamps = requestLog.computeIfAbsent(key,
k -> new ConcurrentLinkedDeque<>());
// Pencere dışındaki eski istekleri temizle
Instant cutoff = Instant.now().minus(window);
while (!timestamps.isEmpty() && timestamps.peekFirst().isBefore(cutoff)) {
timestamps.pollFirst();
}
// Limit kontrolü
if (timestamps.size() >= maxRequests) {
log.warn("🚫 Rate limit exceeded for {}: {}/{} in {}s",
key, timestamps.size(), maxRequests, rateLimited.windowSeconds());
throw new RateLimitExceededException(
String.format("Rate limit exceeded. Max %d requests per %d seconds",
maxRequests, rateLimited.windowSeconds()));
}
timestamps.addLast(Instant.now());
return joinPoint.proceed();
}
}
// ─── Kullanım ───
@RestController
public class AuthController {
@PostMapping("/api/auth/login")
@RateLimited(maxRequests = 5, windowSeconds = 60, key = "login")
public TokenResponse login(@Valid @RequestBody LoginRequest request) {
return authService.authenticate(request);
}
}Kullanım 5: Caching
@Around ile basit bir cache mekanizması:
@Aspect
@Component
@Slf4j
public class SimpleCacheAspect {
private final Map<String, CacheEntry> cache = new ConcurrentHashMap<>();
@Around("@annotation(cached)")
public Object cacheResult(ProceedingJoinPoint joinPoint,
Cached cached) throws Throwable {
String cacheKey = buildCacheKey(joinPoint);
// Cache'de var mı ve süresi dolmamış mı?
CacheEntry entry = cache.get(cacheKey);
if (entry != null && !entry.isExpired()) {
log.debug("📦 Cache HIT: {}", cacheKey);
return entry.value();
}
// Cache miss — metodu çalıştır
log.debug("🔍 Cache MISS: {}", cacheKey);
Object result = joinPoint.proceed();
// Sonucu cache'le
cache.put(cacheKey, new CacheEntry(result,
Instant.now().plusSeconds(cached.ttlSeconds())));
return result;
}
private String buildCacheKey(ProceedingJoinPoint joinPoint) {
return joinPoint.getSignature().toShortString() + ":" +
Arrays.toString(joinPoint.getArgs());
}
private record CacheEntry(Object value, Instant expiresAt) {
boolean isExpired() { return Instant.now().isAfter(expiresAt); }
}
}💡 İpucu: Bu basit cache örneği öğretim amaçlıdır. Production'da Spring'in @Cacheable annotation'ını veya Redis gibi harici cache çözümlerini kullanın.
@Around Advice'ta Yaygın Hatalar
1. ❌ proceed() Sonucunu Return Etmemek
// ❌ YANLIŞ — void metot gibi davranıyor
@Around("execution(* com.example.service.*.*(..))")
public void badAdvice(ProceedingJoinPoint jp) throws Throwable {
jp.proceed(); // Sonuç kayboldu! Caller null alır.
}
// ✅ DOĞRU
@Around("execution(* com.example.service.*.*(..))")
public Object goodAdvice(ProceedingJoinPoint jp) throws Throwable {
return jp.proceed();
}2. ❌ Dönüş Tipini Yanlış Belirlemek
// ❌ YANLIŞ — String dönen metot için Object dönemiyor?
// Aslında Object dönmek doğru, ama cast hatası olabilir
@Around("execution(String com.example.service.*.*(..))")
public Object badCast(ProceedingJoinPoint jp) throws Throwable {
Object result = jp.proceed();
return result.toString().toUpperCase(); // result null olabilir! → NPE
}
// ✅ DOĞRU — null kontrolü
@Around("execution(String com.example.service.*.*(..))")
public Object safeCast(ProceedingJoinPoint jp) throws Throwable {
Object result = jp.proceed();
return result != null ? result.toString().toUpperCase() : null;
}3. ❌ Exception'ı Yutmak
// ❌ YANLIŞ — exception sessizce yok oldu
@Around("execution(* com.example.service.*.*(..))")
public Object swallowException(ProceedingJoinPoint jp) throws Throwable {
try {
return jp.proceed();
} catch (Exception e) {
log.error("Error: {}", e.getMessage());
return null; // Caller hata yerine null alır → daha kötü hatalar
}
}
// ✅ DOĞRU — logla ve tekrar fırlat
@Around("execution(* com.example.service.*.*(..))")
public Object logAndRethrow(ProceedingJoinPoint jp) throws Throwable {
try {
return jp.proceed();
} catch (Exception e) {
log.error("Error in {}: {}", jp.getSignature().toShortString(), e.getMessage());
throw e; // Exception'ı tekrar fırlat
}
}4. ❌ Çok Geniş Pointcut
// ❌ YANLIŞ — TÜM metotları izliyor (toString, hashCode dahil!)
@Around("execution(* *.*(..))")
public Object tooWide(ProceedingJoinPoint jp) throws Throwable { ... }
// ❌ YANLIŞ — Repository metotları dahil (N+1 sorunu tetikleyebilir)
@Around("execution(* com.example..*.*(..))")
public Object stillTooWide(ProceedingJoinPoint jp) throws Throwable { ... }
// ✅ DOĞRU — sadece service katmanı
@Around("execution(* com.example.service.*.*(..))")
public Object focused(ProceedingJoinPoint jp) throws Throwable { ... }
// ✅ EN İYİ — annotation-based, tam kontrol
@Around("@annotation(monitored)")
public Object precise(ProceedingJoinPoint jp, Monitored monitored) throws Throwable { ... }5. ❌ Thread Safety Unutmak
// ❌ YANLIŞ — Aspect singleton'dır, field paylaşılır
@Aspect
@Component
public class BrokenAspect {
private long startTime; // ❌ Thread-unsafe!
@Around("...")
public Object measure(ProceedingJoinPoint jp) throws Throwable {
startTime = System.nanoTime(); // Thread A yazarken Thread B okur!
Object result = jp.proceed();
long elapsed = System.nanoTime() - startTime; // Yanlış Thread'in değeri
return result;
}
}
// ✅ DOĞRU — local variable kullan
@Aspect
@Component
public class SafeAspect {
@Around("...")
public Object measure(ProceedingJoinPoint jp) throws Throwable {
long startTime = System.nanoTime(); // ✅ Her thread kendi değişkenine sahip
Object result = jp.proceed();
long elapsed = System.nanoTime() - startTime;
return result;
}
}Performance Impact — AOP Ne Kadar Yavaşlatır?
AOP advice'ların bir performance maliyeti vardır. Ancak bu maliyet genellikle ihmal edilebilir düzeydedir:
| Advice Türü | Ek Maliyet (yaklaşık) | Açıklama |
|---|---|---|
| @Before / @After | ~0.01ms | Neredeyse sıfır |
| @Around (basit) | ~0.02ms | proceed() çağrısı |
| @Around (logging) | ~0.1ms | String concatenation + log |
| @Around (metrics) | ~0.5ms | Micrometer kayıt |
⚠️ Dikkat: Asıl maliyet AOP'nin kendisinde değil, advice'ın içinde yaptığınız işlemdedir. Advice'ta veritabanı sorgusu, HTTP çağrısı veya dosya okuma yapıyorsanız, bu maliyet çok daha yüksektir.
// ❌ Advice'ta pahalı işlem — YAPMAYIN
@Around("execution(* com.example.service.*.*(..))")
public Object expensiveAdvice(ProceedingJoinPoint jp) throws Throwable {
// Her metot çağrısında veritabanına yazıyor — ÇOK YAVAŞ!
auditRepository.save(new AuditLog(jp.getSignature().toString()));
return jp.proceed();
}
// ✅ Async audit — non-blocking
@Around("execution(* com.example.service.*.*(..))")
public Object asyncAdvice(ProceedingJoinPoint jp) throws Throwable {
Object result = jp.proceed();
// Async olarak kaydet — metodu yavaşlatmaz
CompletableFuture.runAsync(() ->
auditRepository.save(new AuditLog(jp.getSignature().toString())));
return result;
}Gerçek Dünya Örneği: API Request/Response Logger
Tüm API çağrılarını yapılandırılmış şekilde loglayan bütünleşik bir aspect:
@Aspect
@Component
@Slf4j
public class ApiLoggingAspect {
private final ObjectMapper objectMapper;
public ApiLoggingAspect(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Around("execution(* com.example.controller.*Controller.*(..))")
public Object logApiCall(ProceedingJoinPoint joinPoint) throws Throwable {
String controller = joinPoint.getTarget().getClass().getSimpleName();
String method = joinPoint.getSignature().getName();
String requestId = UUID.randomUUID().toString().substring(0, 8);
MDC.put("requestId", requestId);
MDC.put("controller", controller);
MDC.put("method", method);
log.info("→ API Request: {}.{}() args={}",
controller, method, summarize(joinPoint.getArgs()));
long start = System.nanoTime();
try {
Object result = joinPoint.proceed();
long elapsedMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
log.info("← API Response: {}.{}() status=OK duration={}ms",
controller, method, elapsedMs);
return result;
} catch (Throwable ex) {
long elapsedMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
log.error("← API Error: {}.{}() status=ERROR duration={}ms error={}",
controller, method, elapsedMs, ex.getMessage());
throw ex;
} finally {
MDC.clear();
}
}
private String summarize(Object[] args) {
if (args == null || args.length == 0) return "[]";
try {
return objectMapper.writeValueAsString(args);
} catch (Exception e) {
return Arrays.toString(args);
}
}
}Özet
`@Around` en güçlü advice türüdür — metodu tamamen sarar: önce, sonra, hata durumu ve hatta metodu çağırmama seçeneği
`ProceedingJoinPoint.proceed()` ile hedef metot çalıştırılır — sonucu mutlaka return edin
Performance monitoring:
System.nanoTime()ile süre ölçümü, eşik seviyelerine göre uyarıRetry mekanizması: Geçici hatalar için exponential backoff ile yeniden deneme
Rate limiting: Metot çağrı sıklığını sınırlama
Thread safety: Aspect singleton'dır — instance variable kullanmayın, local variable kullanın
Principle of least power: Sadece loglama gerekiyorsa
@Beforeyeterli,@AroundkullanmayınAdvice'ta pahalı işlem yapmayın — loglama kısa tutun, ağır işleri async yapın
AI Asistan
Sorularını yanıtlamaya hazır