Eğitim Portalı/Spring/Bean Yaşam Döngüsü ve Scope
Spring02-spring/02-bean-yasam-dongusu-ve-scope

Bean Yaşam Döngüsü ve Scope

Container'ın bean'leri oluşturup bağladığını biliyoruz. Peki bu bean'ler tam olarak ne zaman oluşturulur, ne zaman yok edilir ve container içinde kaç tane kopyaları olur? Bu sorular yaşam döngüsü (lifecycle) ve kapsam…

Bean Yaşam Döngüsü ve Scope

Container'ın bean'leri oluşturup bağladığını biliyoruz. Peki bu bean'ler tam olarak ne zaman oluşturulur, ne zaman yok edilir ve container içinde kaç tane kopyaları olur? Bu sorular yaşam döngüsü (lifecycle) ve kapsam (scope) ile cevaplanır. Bunları anlamak; kaynakları doğru açıp kapatmak ve durumlu/durumsuz nesneleri doğru yönetmek için kritiktir.

Bean yaşam döngüsü

Bir bean dünyaya gelip giderken belirli aşamalardan geçer ve sen bu aşamalara kanca (callback) takabilirsin. Tipik sıra şudur:

  1. Constructor çağrılır (nesne oluşur).
  2. Bağımlılıklar enjekte edilir.
  3. Başlatma (init): @PostConstructafterPropertiesSet() (InitializingBean) → @Bean initMethod.
  4. Bean kullanılır.
  5. Yok etme (destroy): Container kapanırken @PreDestroydestroy() (DisposableBean) → @Bean destroyMethod.
@Component
class BaglantiHavuzu {
    @PostConstruct void baslat() { /* kaynağı aç */ }
    @PreDestroy   void kapat()  { /* kaynağı kapat */ }
}

@PostConstruct/@PreDestroy (jakarta.annotation) en yaygın ve framework-bağımsız yoldur; InitializingBean/DisposableBean arayüzleri Spring'e özeldir; @Bean(initMethod=..., destroyMethod=...) ise üçüncü-parti sınıflar için kullanışlıdır. Örnek 1 (./Ornek1.java) bir bağlantı havuzunu modelleyerek tüm sırayı çıktıyla gösterir — @PostConstruct'ta açılır, context kapanınca @PreDestroy'da kapanır.

Destroy geri çağrılarının çalışması için context'in düzgün kapatılması gerekir (ctx.close()). prototype kapsamındaki bean'lerde Spring destroy'u yönetmez (aşağıya bak).

Bean scope'ları

Scope, container'ın bir bean tanımından kaç örnek oluşturacağını ve bunların ömrünü belirler.

singleton (varsayılan)

Spring'de bean'lerin varsayılan kapsamıdır: container ömrü boyunca tek bir örnek oluşturulur ve her istekte aynı nesne döner.

@Component class AyarServisi { } // varsayılan: singleton

Durumsuz (stateless) servisler, repository'ler ve yapılandırma için idealdir — ki uygulamadaki bean'lerin büyük çoğunluğu böyledir.

prototype

Her getBean() (veya her enjeksiyon) çağrısında yeni bir örnek oluşturulur. Durumlu (stateful), her kullanımda taze olması gereken nesneler içindir.

@Component @Scope("prototype") class SepetNesnesi { }

Örnek 2 (./Ornek2.java) ikisini kanıtlar: singleton'da a1 == a2 (aynı nesne), prototype'ta s1 != s2 (farklı nesneler).

Web kapsamları

Web uygulamalarında ek kapsamlar vardır: request (her HTTP isteği için bir bean) ve session (her kullanıcı oturumu için bir bean). Bunları Spring Boot/web bölümünde göreceğiz.

ScopeÖrnek sayısıTipik kullanım
singletonContainer'da tekDurumsuz servis, repository (varsayılan)
prototypeHer istekte yeniDurumlu, kısa ömürlü nesne
requestHer HTTP isteğindeİstek bazlı veri (web)
sessionHer oturumdaKullanıcıya özel veri (web)

Klasik tuzak: singleton içinde prototype

Çok yapılan bir hata: bir singleton servis, bir prototype bean'i constructor'da bir kez enjekte eder. O prototype yalnızca bir kez oluşturulduğundan, "her seferinde taze nesne" beklentisi boşa çıkar. Örnek 3 (./Ornek3.java) bu tuzağı gösterir ve çözer: ObjectProvider<T> (veya @Lookup) ile prototype'ı ihtiyaç anında container'dan taze isteyerek.

@Service class DogruIsleyici {
    private final ObjectProvider<Gorev> saglayici;
    int calistir() { return saglayici.getObject().id(); } // her çağrıda TAZE prototype
}

Özet

Bean yaşam döngüsünü (constructor → enjeksiyon → init → kullanım → destroy) ve buna kanca takmayı (@PostConstruct/@PreDestroy); scope'ları (varsayılan singleton, prototype ve web kapsamları) ve singleton-içinde-prototype tuzağını ObjectProvider ile çözmeyi öğrendik. Sırada, bu bean'leri container'a tanıtmanın farklı yolları: yapılandırma yöntemleri ve stereotype anotasyonları.

Kod Örnekleri(3)

Ornek1

ortam gerekir
Ornek1.java
1// Ornek1: Bean yaşam döngüsü — başlatma (init) ve yok etme (destroy) geri çağrıları.
2// Gerçek senaryo: bir bağlantı havuzu init'te açılır, context kapanınca kapatılır.
3// Çalıştırma: java Ornek1.java
4import jakarta.annotation.PostConstruct;
5import jakarta.annotation.PreDestroy;
6import org.springframework.beans.factory.InitializingBean;
7import org.springframework.beans.factory.DisposableBean;
8import org.springframework.context.annotation.AnnotationConfigApplicationContext;
9import org.springframework.stereotype.Component;
10
11public class Ornek1 {
12
13    public static void main(String[] args) {
14        System.out.println(">> Container başlatılıyor...");
15        var ctx = new AnnotationConfigApplicationContext();
16        ctx.register(BaglantiHavuzu.class);
17        ctx.refresh(); // bean'ler oluşturulur ve init geri çağrıları tetiklenir
18
19        System.out.println("\n>> Uygulama çalışıyor:");
20        ctx.getBean(BaglantiHavuzu.class).baglantiAl();
21
22        System.out.println("\n>> Container kapatılıyor...");
23        ctx.close(); // destroy geri çağrıları tetiklenir
24
25        System.out.println("""
26
27                --- Yaşam döngüsü sırası ---
28                1) Constructor       (nesne oluşturulur)
29                2) Bağımlılık enjeksiyonu
30                3) @PostConstruct / afterPropertiesSet() (başlatma)
31                4) ... bean kullanılır ...
32                5) @PreDestroy / destroy()              (temizlik, context kapanınca)""");
33    }
34}
35
36@Component
37class BaglantiHavuzu implements InitializingBean, DisposableBean {
38
39    BaglantiHavuzu() {
40        System.out.println("1) Constructor: havuz nesnesi oluşturuldu (henüz açık değil).");
41    }
42
43    // jakarta.annotation: en yaygın, framework-bağımsız başlatma kancası.
44    @PostConstruct
45    void baslat() {
46        System.out.println("3a) @PostConstruct: bağlantı havuzu AÇILDI.");
47    }
48
49    // Spring arayüzü: aynı işi yapar (@PostConstruct'tan sonra çağrılır).
50    @Override
51    public void afterPropertiesSet() {
52        System.out.println("3b) afterPropertiesSet(): havuz hazır.");
53    }
54
55    void baglantiAl() {
56        System.out.println("   -> havuzdan bağlantı verildi.");
57    }
58
59    @PreDestroy
60    void kapat() {
61        System.out.println("5a) @PreDestroy: bağlantı havuzu KAPATILIYOR.");
62    }
63
64    @Override
65    public void destroy() {
66        System.out.println("5b) destroy(): tüm kaynaklar serbest bırakıldı.");
67    }
68}
canlı çalıştırma için ortam gerekir

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 gerekir
Ornek2.java
1// Ornek2: Bean scope'ları — singleton (varsayılan) vs prototype.
2// Çalıştırma: java Ornek2.java
3import org.springframework.context.annotation.AnnotationConfigApplicationContext;
4import org.springframework.context.annotation.Scope;
5import org.springframework.stereotype.Component;
6
7public class Ornek2 {
8
9    public static void main(String[] args) {
10        var ctx = new AnnotationConfigApplicationContext();
11        ctx.register(AyarServisi.class, SepetNesnesi.class);
12        ctx.refresh();
13
14        // --- SINGLETON (varsayılan): container ömrü boyunca TEK örnek ---
15        AyarServisi a1 = ctx.getBean(AyarServisi.class);
16        AyarServisi a2 = ctx.getBean(AyarServisi.class);
17        System.out.println("Singleton:");
18        System.out.println("  a1 == a2 ? " + (a1 == a2) + "   (aynı nesne)");
19        System.out.println("  a1.id = " + a1.id() + ", a2.id = " + a2.id());
20
21        // --- PROTOTYPE: her getBean()'de YENİ örnek ---
22        SepetNesnesi s1 = ctx.getBean(SepetNesnesi.class);
23        SepetNesnesi s2 = ctx.getBean(SepetNesnesi.class);
24        System.out.println("\nPrototype:");
25        System.out.println("  s1 == s2 ? " + (s1 == s2) + "  (farklı nesneler)");
26        System.out.println("  s1.id = " + s1.id() + ", s2.id = " + s2.id());
27
28        ctx.close();
29
30        System.out.println("""
31
32                --- Ne zaman hangisi? ---
33                singleton : durumsuz (stateless) servisler, repository'ler, yapılandırma.
34                            (Spring'de bean'lerin VARSAYILAN kapsamı budur.)
35                prototype : her kullanımda taze, durumlu (stateful) nesneler (ör. bir sepet, bir form).
36                Web'de ayrıca 'request' ve 'session' kapsamları da vardır (her istek/oturum için bir bean).""");
37    }
38}
39
40// Varsayılan kapsam singleton'dır; @Scope yazmaya gerek yok.
41@Component
42class AyarServisi {
43    private final int id = System.identityHashCode(this);
44    int id() { return id; }
45}
46
47@Component
48@Scope("prototype") // her istekte yeni nesne
49class SepetNesnesi {
50    private final int id = System.identityHashCode(this);
51    int id() { return id; }
52}
canlı çalıştırma için ortam gerekir

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 gerekir
Ornek3.java
1// Ornek3: Gerçek tuzak — singleton bir bean, prototype bir bean'e ihtiyaç duyarsa ne olur?
2// Çözüm: ObjectProvider ile her seferinde TAZE prototype almak.
3// Çalıştırma: java Ornek3.java
4import org.springframework.beans.factory.ObjectProvider;
5import org.springframework.context.annotation.AnnotationConfigApplicationContext;
6import org.springframework.context.annotation.Scope;
7import org.springframework.stereotype.Component;
8import org.springframework.stereotype.Service;
9
10public class Ornek3 {
11
12    public static void main(String[] args) {
13        var ctx = new AnnotationConfigApplicationContext();
14        ctx.register(Gorev.class, YanlisIsleyici.class, DogruIsleyici.class);
15        ctx.refresh();
16
17        // TUZAK: singleton servis, prototype'ı constructor'da BİR KEZ aldı.
18        // Aynı prototype örneği tekrar tekrar kullanılır -> "taze nesne" beklentisi boşa çıkar.
19        System.out.println("Yanlış (prototype bir kez enjekte edildi):");
20        YanlisIsleyici yanlis = ctx.getBean(YanlisIsleyici.class);
21        System.out.println("  görev id: " + yanlis.calistir());
22        System.out.println("  görev id: " + yanlis.calistir() + "   <- AYNI nesne (tuzak!)");
23
24        // ÇÖZÜM: ObjectProvider ile her ihtiyaçta container'dan TAZE prototype iste.
25        System.out.println("\nDoğru (ObjectProvider ile her seferinde taze):");
26        DogruIsleyici dogru = ctx.getBean(DogruIsleyici.class);
27        System.out.println("  görev id: " + dogru.calistir());
28        System.out.println("  görev id: " + dogru.calistir() + "   <- FARKLI nesne (doğru)");
29
30        ctx.close();
31
32        System.out.println("""
33
34                --- Çıkarım ---
35                Singleton bir bean, prototype'ı SADECE BİR KEZ (kurulumda) enjekte ederse,
36                prototype'ın 'her seferinde yeni' avantajı kaybolur.
37                Çözümlerden biri: ObjectProvider<T> (veya @Lookup) ile ihtiyaç anında taze örnek almak.""");
38    }
39}
40
41@Component
42@Scope("prototype")
43class Gorev {
44    private final int id = System.identityHashCode(this);
45    int id() { return id; }
46}
47
48@Service // singleton
49class YanlisIsleyici {
50    private final Gorev gorev; // prototype ama yalnızca BİR KEZ enjekte edilir
51    YanlisIsleyici(Gorev gorev) { this.gorev = gorev; }
52    int calistir() { return gorev.id(); }
53}
54
55@Service // singleton
56class DogruIsleyici {
57    private final ObjectProvider<Gorev> gorevSaglayici; // her çağrıda taze üretebilir
58    DogruIsleyici(ObjectProvider<Gorev> gorevSaglayici) { this.gorevSaglayici = gorevSaglayici; }
59    int calistir() { return gorevSaglayici.getObject().id(); } // TAZE prototype
60}
canlı çalıştırma için ortam gerekir

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.