Bean Yaşam Döngüsü ve Scope'lar
Önce şunu oku: IoC ve Dependency Injection
30 saniyede özet
Spring bir nesneyi birkaç adımda hazırlar ve @Transactional gibi etiketleri çalıştıran aracı (proxy) en son eklenir. Bu yüzden @PostConstruct içinden çağrılan @Transactional sessizce hiçbir şey yapmaz.
Spring bir nesneyi tek hamlede değil, işe yeni başlayan biri gibi adım adım hazırlar. Bu sırayı bilmek, iki can sıkıcı hatanın sebebini açıklar.
Adım adım oku
- Spring önce kurucuyu çağırır, sonra bağımlılıkları verir: sözleşme imzalandı, bilgisayar teslim edildi.
- Ardından @PostConstruct çalışır: oryantasyon günü.
- Oryantasyonda @Transactional bir metodu çağırırsan çağrı çıplak nesneye gider; transaction açılmaz.
- @Transactional'ı çalıştıran proxy en son eklenir. Ancak ondan sonra dışarıdan gelen çağrılar proxy'den geçer.
-
Bayt: @PostConstruct içinde başlangıç verisini yazan @Transactional bir metodu çağırdım.
-
Sen: Güzel, uygulama açılınca tablo hazır olur.
-
Bayt: Olmadı! Transaction hiç açılmamış, yarım kayıtlar kalmış.
-
Bayt: Oryantasyon günü şirket kartıyla kapı açılmaz: kart henüz basılmadı.
Hangi adım ne zaman?
Bir bean'in @PostConstruct metodu çalışırken, constructor'dan değil alan enjeksiyonuyla gelen bağımlılıklar dolu mudur? Cevabı göster
Evet. @PostConstruct, bütün enjeksiyon bittikten sonra çalışır; hazırlık için doğru yer de bu yüzden orasıdır.
1. Kurucu2. Bağımlılık enjeksiyonu3. Aware geri çağrıları (BeanNameAware, ApplicationContextAware)4. BeanPostProcessor.postProcessBeforeInitialization()5. @PostConstruct6. InitializingBean.afterPropertiesSet()7. @Bean(initMethod = "...")8. BeanPostProcessor.postProcessAfterInitialization() ← AOP PROXY BURADA OLUŞUR9. Bean hazır ...10. @PreDestroy11. DisposableBean.destroy()12. @Bean(destroyMethod = "...")Dikkat edilecek yer 8. adım. Proxy, bütün başlatma geri çağrıları bittikten sonra oluşturulur.
Kafam karıştı, daha basit anlat
Bean’in hayatı bir çalışanın ilk günü gibidir: önce işe alınır, sonra araçları verilir, sonra hazırlık yapar. En sonunda işten ayrılırken masasını toplar.
Bir bean'in başlatma kancaları hangi sırayla çalışır?
Kendin izle
Bean yaşam döngüsü — sıra, proxy ve scope
Tohum 1Oluşturma
Kurucu çağrılırBağımlılıklar enjekte edilirAware geri çağrıları
BeanNameAware.setBeanName()ApplicationContextAware.setApplicationContext()BeanPostProcessor
BeanPostProcessor.postProcessBeforeInitialization()Başlatma geri çağrıları
@PostConstructproxy yok — sessiz hataInitializingBean.afterPropertiesSet()@Bean(initMethod = "...")BeanPostProcessor
BeanPostProcessor.postProcessAfterInitialization()proxy buradaKullanım
Bean kullanıma hazırKapatma
@PreDestroyDisposableBean.destroy()@Bean(destroyMethod = "...")
Şu an ne oldu?
Kurucu çalışıyor
Nesne oluşturuluyor. Constructor injection kullanılıyorsa bağımlılıklar tam burada verilir, field injection kullanılıyorsa alanlar hâlâ null.
Aklında kalsın: Kurucuda bağımlılık kullanabilmek, constructor injection’ın field injection karşısındaki somut avantajlarından biridir.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
İki ayarı mutlaka dene:
- Varsayılan —
@PostConstructadımında kırmızı bir satır çıkıyor. Proxy henüz yok,@Transactionalçağrısı ham nesneye gitti, transaction açılmadı. Hiçbir hata da verilmedi. - Scope’u
prototypeyap. Üç destroy adımı üstü çizili geçiliyor. Spring bu örneği teslim edip unuttu.
Kafam karıştı, daha basit anlat
Hazırlık adımı, bütün araçlar verildikten sonra çalışır. Araçlar gelmeden hazırlanmaya çalışırsan elin boş kalır.
Bu bean açılışta ne yazdırır?
Tuzaklar
@Servicepublic class CacheWarmer {
@PostConstruct void warmUp() { loadFromDb(); // @Transactional ama proxy henüz yok → transaction YOK }
@Transactional public void loadFromDb() { /* ... */ }}İki sorun birden var. Birincisi bu bir self-invocation — this.loadFromDb() proxySpring'in bean'in yerine koyduğu sarmalayıcı nesne. `@Transactional` gibi anotasyonlar burada çalışır — çağrı proxy'den geçmezse hiçbir şey olmaz.Sözlükte gör →’den geçmez. İkincisi, dışarıdan çağrılsa bile proxy henüz var olmadığı için yine çalışmazdı.
Doğru çözüm, başlatmayı tüm proxy’ler hazır olduktan sonraya ertelemektir:
@Servicepublic class CacheWarmer {
@EventListener(ApplicationReadyEvent.class) // container tamamen hazır void warmUp() { cacheService.loadFromDb(); // başka bir bean → proxy devrede }}Aşağıdaki örnek bir bankanın gün sonu servisinden. Her kanca doğru işte: SFTP bağlantısını açıp kapatmak, gelen ödemeleri işleyen döngüyü sırayla başlatıp durdurmak, günün kurlarını uygulama hazır olunca yüklemek ve her ekstre için yeni bir yazıcı kullanmak.
Derinleş · Bankanın gün sonu servisi: hangi kanca, ne zaman? 5 dosya · ~172 satır · ilk okumada atlayabilirsin
Prototype beanYaşam döngüsünü Spring konteynerinin yönettiği nesne. Sen `new` demezsin; konteyner kurar, bağımlılıklarını verir ve gerekirse yok eder.Sözlükte gör →’lerin destroy’u çalışmaz. Spring prototype bean’i oluşturur, bağımlılıklarını enjekte eder, sana verir ve takibi bırakır. Örneği artık tutmadığı için:
@PreDestroyasla çağrılmazDisposableBean.destroy()asla çağrılmazdestroyMethodasla çağrılmaz
@Scope("prototype")@Componentpublic class ReportGenerator implements AutoCloseable {
private final FileWriter writer = new FileWriter("/tmp/report");
@PreDestroy public void cleanup() throws IOException { writer.close(); // ASLA ÇAĞRILMAZ }}Prototype bir bean kaynak tutuyorsa onu kapatmak çağıranın sorumluluğudur — try-with-resources veya açık bir close() çağrısı gerekir.
`@PostConstruct` içinden aynı bean'in `@Transactional` metodunu çağırıyorsunuz. Ne olur?
`@PreDestroy` hangi durumda hiç çalışmaz?
Scope’lar
| Scope | Kaç örnek | destroy çalışır mı |
|---|---|---|
singleton (varsayılan) | Container başına bir | Evet |
prototype | Her enjeksiyon/istekte yeni | Hayır |
request | Her HTTP isteğinde bir | Evet (istek bitince) |
session | Her oturumda bir | Evet (oturum bitince) |
Singleton olmanın en önemli sonucu: bean’ler durumsuz olmalıdır. Tek bir örnek tüm eşzamanlı istekleri karşılar, dolayısıyla bir alana istek verisi yazmak doğrudan bir yarış durumudur.
@Servicepublic class OrderService { // singleton — bu alan tüm istekler arasında paylaşılıyor private Order currentOrder; // yarış durumu
public void process(Order order) { this.currentOrder = order; // başka bir istek bunu ezebilir }}Kafam karıştı, daha basit anlat
Singleton bean, bütün müşterilere hizmet eden tek bir çalışandır. Bir müşterinin bilgisini onun masasına yazarsan, sonraki müşteri onu okur.
Varsayılan bean kapsamı (`scope`) nedir ve ne anlama gelir?
Singleton bir bean'de istek verisini bir alana yazmak neden sorunludur?
Üç hazırlık yöntemi, hangisi?
| Spring’e bağımlılık | Not | |
|---|---|---|
@PostConstruct | Yok (JSR-250 standardı) | Tercih edilen |
InitializingBean | Var — arayüzü implemente etmek gerekir | Sınıfı Spring’e bağlar |
@Bean(initMethod) | Yok | Kaynak kodu sende olmayan sınıflar için |
Modern kodda genellikle yalnızca @PostConstruct görürsün. @Bean(initMethod) ise üçüncü parti bir sınıfı yapılandırırken işe yarar — sınıfa anotasyon ekleyemezsin ama yapılandırmada init metodunu söyleyebilirsin.
Kendini sına
Spring bir bean'i tek hamlede hazırlar.
Prototype scope'lu bir bean'de `@PreDestroy` ne zaman çağrılır?
Aklında kalacak üç şey
- 1 Hazırlık sırası: önce @PostConstruct, sonra afterPropertiesSet(), sonra initMethod. Proxy bunların hepsinden sonra eklenir.
- 2 Proxy en sonda geldiği için @PostConstruct içinden çağrılan @Transactional metot ham nesneye gider: transaction hiç açılmaz, hata da verilmez.
- 3 Prototype scope'ta Spring nesneyi teslim edip unutur, @PreDestroy hiç çalışmaz. Kaynağı kapatmak çağıranın işidir.
4 kart sonraki derste seni bekliyor