Caching, Async ve Scheduling
Bir uygulamayı hızlı ve verimli kılmanın üç güçlü aracını ele alıyoruz: tekrarlayan pahalı işlemleri önbelleğe almak (@Cacheable), uzun süren işleri arka plana atmak (@Async) ve belirli aralıklarla otomatik görev çalı…
Caching, Async ve Scheduling
Bir uygulamayı hızlı ve verimli kılmanın üç güçlü aracını ele alıyoruz: tekrarlayan pahalı
işlemleri önbelleğe almak (@Cacheable), uzun süren işleri arka plana atmak (@Async)
ve belirli aralıklarla otomatik görev çalıştırmak (@Scheduled). Üçü de Spring'in AOP/proxy
mekanizması üzerine kuruludur — yani bir anotasyon eklersin, gerisini Spring halleder.
@Cacheable — önbellekleme
Aynı veriyi tekrar tekrar hesaplamak (veritabanı sorgusu, dış API çağrısı, ağır hesap) israftır.
@Cacheable, bir metodun sonucunu anahtarına göre saklar; aynı argümanla ikinci çağrıda metot
hiç çalışmaz, sonuç doğrudan önbellekten döner:
@EnableCaching // altyapıyı aç @Cacheable("urunler") public String detayGetir(Long id) { /* pahalı işlem */ }
Örnek 1 (./Ornek1.java) aynı id ile ilk çağrının ~600 ms, ikinci çağrının ~0 ms sürdüğünü
(önbellekten) canlı ölçer. Farklı id, anahtarı değiştirdiğinden tekrar hesaplanır.
Spring Boot, bir cache sağlayıcısı yoksa basit bir bellek-içi (
ConcurrentMapCacheManager) önbellek yapılandırır. Üretimde Redis, Caffeine gibi sağlayıcılarla dağıtık/gelişmiş önbellek kullanılır. İlgili anotasyonlar:@CacheEvict(önbelleği temizle),@CachePut(güncelle),@Cacheable(condition=..., unless=...).
@Async — asenkron çalıştırma
Bir e-posta göndermek veya rapor üretmek için kullanıcıyı bekletmek istemezsin. @Async, bir
metodu ayrı bir thread'de, çağrıyı bloklamadan çalıştırır:
@EnableAsync @Async public CompletableFuture<String> raporUret(int no) { /* uzun iş */ }
Metot çağrılır çağrılmaz hemen döner; iş arka planda sürer. CompletableFuture döndürerek
sonucu sonra toplayabilirsin. Örnek 2 (./Ornek2.java) üç raporu aynı anda tetikler ve toplam
sürenin ~500 ms (sıralı olsaydı ~1500 ms) olduğunu gösterir — işler paralel koştu.
Dikkat:
@Asyncda proxy tabanlıdır; self-invocation (aynı sınıf içi çağrı) asenkron çalışmaz. Ayrıca üretimde kendiTaskExecutor(thread havuzu) yapılandırmanı tanımlamak iyidir (varsayılan havuz sınırlıdır).
@Scheduled — zamanlanmış görevler
Periyodik işler (gece yarısı rapor, her 5 dakikada sağlık kontrolü, eski kayıt temizliği)
@Scheduled ile tanımlanır:
@EnableScheduling @Scheduled(fixedRate = 1500) // her 1.5 sn public void periyodikGorev() { ... } @Scheduled(cron = "0 0 3 * * *") // her gün saat 03:00 (cron ifadesi) public void geceBakimi() { ... }
fixedRate— sabit aralıkla (önceki bitişi beklemeden).fixedDelay— önceki bitişten itibaren belirli gecikmeyle.initialDelay— ilk çalışmadan önceki gecikme.cron— takvim bazlı ifadeyle (saat/gün/ay).
Örnek 3 (./Ornek3.java) iki zamanlanmış görevi başlatır; uygulama açık kaldığı birkaç saniye
boyunca tikler arka planda otomatik üretilir (bu örnekte çıktı, self-test'ten değil
zamanlayıcıdan gelir).
Özet
@Cacheable ile pahalı işlemleri önbelleğe almayı (Örnek 1), @Async ile işleri arka plana
atıp paralel çalıştırmayı (Örnek 2) ve @Scheduled ile periyodik görevleri (Örnek 3) gerçek,
çalışan uygulamalarla gördük. Üçü de proxy tabanlıdır (self-invocation tuzağı geçerli). Bu
araçlar, uygulamanın yanıt süresini ve verimliliğini ciddi biçimde iyileştirir. Sırada,
uygulamayı izlemek ve üretime hazırlamak: Actuator ve üretim araçları.
▶ Kod Örnekleri(3)
Ornek1
ortam gerekir1// Ornek1: @Cacheable — pahalı bir işlemin sonucunu önbelleğe alıp tekrar tekrar hesaplamamak.
2// Çalıştırma: portal derleyip gömülü Tomcat ile başlatır.
3package com.egitim.springboot.cache;
4
5import org.springframework.boot.CommandLineRunner;
6import org.springframework.boot.SpringApplication;
7import org.springframework.boot.autoconfigure.SpringBootApplication;
8import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
9import org.springframework.cache.annotation.Cacheable;
10import org.springframework.cache.annotation.EnableCaching;
11import org.springframework.stereotype.Service;
12
13@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
14@EnableCaching // önbellekleme altyapısını açar (Boot basit bir bellek-içi cache yapılandırır)
15public class Ornek1 {
16
17 public static void main(String[] args) { SpringApplication.run(Ornek1.class, args); }
18
19 @Service
20 public static class UrunServisi {
21 // @Cacheable: aynı id ile ilk çağrıda metot çalışır ve sonuç "urunler" cache'ine yazılır;
22 // sonraki çağrılarda metot HİÇ çalışmaz, sonuç doğrudan cache'ten döner.
23 @Cacheable("urunler")
24 public String detayGetir(Long id) {
25 yavasIslem(); // pahalı işlemi (DB/ağ) taklit eder
26 return "Ürün-" + id + " detayları";
27 }
28
29 private void yavasIslem() {
30 try { Thread.sleep(600); } catch (InterruptedException ignored) {}
31 }
32 }
33
34 @org.springframework.context.annotation.Bean
35 CommandLineRunner selfTest(UrunServisi servis) {
36 return args -> {
37 System.out.println("\n================ @Cacheable ================");
38 System.out.println("1. çağrı (id=1): " + olcveCagir(servis, 1L));
39 System.out.println("2. çağrı (id=1): " + olcveCagir(servis, 1L) + " <- cache'ten, anında!");
40 System.out.println("3. çağrı (id=2): " + olcveCagir(servis, 2L) + " <- yeni id, tekrar hesaplandı");
41 System.out.println("4. çağrı (id=1): " + olcveCagir(servis, 1L) + " <- yine cache'ten");
42 System.out.println("============================================");
43 System.out.println("@Cacheable, pahalı (DB/ağ/hesap) işlemleri tekrarlamamayı sağlar. Redis ile dağıtık da olabilir.");
44 };
45 }
46
47 static String olcveCagir(UrunServisi s, Long id) {
48 long t0 = System.currentTimeMillis();
49 String sonuc = s.detayGetir(id);
50 return "(" + (System.currentTimeMillis() - t0) + " ms) " + sonuc;
51 }
52}Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek1.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.
Ornek2
ortam gerekir1// Ornek2: @Async — uzun süren işleri ayrı thread'lerde, bloklamadan çalıştırmak.
2// Çalıştırma: portal derleyip gömülü Tomcat ile başlatır.
3package com.egitim.springboot.cache;
4
5import org.springframework.boot.CommandLineRunner;
6import org.springframework.boot.SpringApplication;
7import org.springframework.boot.autoconfigure.SpringBootApplication;
8import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
9import org.springframework.context.annotation.Bean;
10import org.springframework.scheduling.annotation.Async;
11import org.springframework.scheduling.annotation.EnableAsync;
12import org.springframework.stereotype.Service;
13
14import java.util.concurrent.CompletableFuture;
15
16@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
17@EnableAsync // @Async metotları ayrı thread havuzunda çalıştırır
18public class Ornek2 {
19
20 public static void main(String[] args) { SpringApplication.run(Ornek2.class, args); }
21
22 @Service
23 public static class RaporServisi {
24 // @Async: bu metot çağrıldığında HEMEN döner; iş arka planda (başka thread'de) çalışır.
25 // CompletableFuture döndürerek sonucu sonra toplayabiliriz.
26 @Async
27 public CompletableFuture<String> raporUret(int no) {
28 try { Thread.sleep(500); } catch (InterruptedException ignored) {} // ağır iş
29 String thread = Thread.currentThread().getName();
30 return CompletableFuture.completedFuture("Rapor-" + no + " (thread: " + thread + ")");
31 }
32 }
33
34 @Bean
35 CommandLineRunner selfTest(RaporServisi servis) {
36 return args -> {
37 System.out.println("\n================ @Async ================");
38 long t0 = System.currentTimeMillis();
39
40 // Üç raporu AYNI ANDA tetikle (her biri ~500 ms). Çağrılar hemen döner.
41 CompletableFuture<String> r1 = servis.raporUret(1);
42 CompletableFuture<String> r2 = servis.raporUret(2);
43 CompletableFuture<String> r3 = servis.raporUret(3);
44
45 // Hepsinin bitmesini bekle ve sonuçları yazdır.
46 CompletableFuture.allOf(r1, r2, r3).join();
47 System.out.println(" " + r1.get());
48 System.out.println(" " + r2.get());
49 System.out.println(" " + r3.get());
50
51 long sure = System.currentTimeMillis() - t0;
52 System.out.println("Toplam süre: ~" + sure + " ms (sıralı olsaydı ~1500 ms olurdu)");
53 System.out.println("========================================");
54 System.out.println("@Async: e-posta gönderme, rapor üretme gibi işleri arka plana atıp isteği bekletmemek için.");
55 };
56 }
57}Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek2.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.
Ornek3
ortam gerekir1// Ornek3: @Scheduled — belirli aralıklarla otomatik çalışan görevler (zamanlama).
2// Çalıştırma: portal derleyip gömülü Tomcat ile başlatır; birkaç saniye boyunca tetiklenir.
3package com.egitim.springboot.cache;
4
5import org.springframework.boot.SpringApplication;
6import org.springframework.boot.autoconfigure.SpringBootApplication;
7import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
8import org.springframework.scheduling.annotation.EnableScheduling;
9import org.springframework.scheduling.annotation.Scheduled;
10import org.springframework.stereotype.Component;
11
12import java.time.LocalTime;
13import java.time.format.DateTimeFormatter;
14import java.util.concurrent.atomic.AtomicInteger;
15
16@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
17@EnableScheduling // zamanlanmış görevleri etkinleştirir
18public class Ornek3 {
19
20 public static void main(String[] args) { SpringApplication.run(Ornek3.class, args); }
21
22 @Component
23 public static class ZamanlanmisGorevler {
24 private final AtomicInteger sayac = new AtomicInteger();
25 private static final DateTimeFormatter SAAT = DateTimeFormatter.ofPattern("HH:mm:ss");
26
27 // fixedRate: her 1500 ms'de bir çalışır (önceki bitişe bakmadan, sabit aralıkla).
28 @Scheduled(fixedRate = 1500)
29 public void periyodikGorev() {
30 System.out.println(" [zamanlanmış] tik #" + sayac.incrementAndGet()
31 + " @ " + LocalTime.now().format(SAAT));
32 }
33
34 // initialDelay + fixedRate: ilk çalışma 500 ms gecikmeli başlar.
35 @Scheduled(initialDelay = 500, fixedRate = 2000)
36 public void digerGorev() {
37 System.out.println(" [zamanlanmış-2] yedekleme kontrolü @ " + LocalTime.now().format(SAAT));
38 }
39 }
40
41 // Not: Bu örnekte ana çıktı, arka planda çalışan @Scheduled metotlarından gelir.
42 // Uygulama açık kaldığı süre boyunca (birkaç saniye) görevler tetiklenir, sonra durdurulur.
43 @org.springframework.context.annotation.Bean
44 org.springframework.boot.CommandLineRunner aciklama() {
45 return args -> {
46 System.out.println("\n================ @Scheduled ================");
47 System.out.println("Zamanlanmış görevler başladı. Aşağıdaki tikler OTOMATİK, arka planda üretilir:");
48 System.out.println("(@Scheduled(fixedRate=1500) ve initialDelay'li ikinci görev)");
49 System.out.println("--------------------------------------------");
50 };
51 }
52}Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek3.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.