IoC ve Dependency Injection
30 saniyede özet
Bir sınıf ihtiyaç duyduğu nesneleri kendisi yaratmaz, dışarıdan ister; Spring de onları getirip verir. Bunları constructor ile istemek en güvenlisi: bir döngü varsa uygulama açılırken hemen fark edersin.
Bir usta işe başlamadan önce kendi çekicini dövmez; çekiç ona verilir. Kodda da böyle: sınıf ihtiyaç duyduğu nesneyi kendisi yaratmaz, dışarıdan alır. Bunun adı Inversion of Control.
Adım adım oku
- OrderService ödeme istemcisini kendisi yaratıyor: new StripeClient(). Araç sınıfa kaynaklı.
- Başka bir istemci kullanmak, örneğin testte sahte bir tane, sınıfın içini değiştirmeyi gerektirir.
- Dependency injection'da sınıf aracını constructor'da ister; Spring container onu bulup verir.
- Testte container'a sahte istemciyi vermek yeter. OrderService'in tek satırı bile değişmez.
-
Bayt: OrderService'im ödeme istemcisini kendisi yaratıyor: new StripePaymentClient().
-
Sen: Testte gerçek para çekmek istemiyorum. Ne yapacağım?
-
Bayt: Sınıfın içini değiştirmeden değiştiremezsin; araç sınıfa kaynaklanmış!
-
Bayt: Usta çekicini dövmez, çekiç ona verilir. Spring de tam olarak bunu yapıyor.
public class OrderService { // Bu sınıf hangi implementasyonu kullanacağına kendi karar veriyor private final PaymentClient client = new StripePaymentClient();}public class OrderService { private final PaymentClient client;
public OrderService(PaymentClient client) { // kim olduğunu bilmiyor, verilmesini bekliyor this.client = client; }}İkinci sürüm testte sahte bir istemciyle, üretimde gerçeğiyle çalışır. Dependency Injection, IoC’yi uygulama yöntemidir; Spring container ise bunu yapan şey.
Spring perde arkasında ne yapıyor?
OrderService, constructor'ında bir PaymentClient istiyor. Spring bu ikisinden hangisini önce kurar? Cevabı göster
PaymentClient. OrderService’e verilecek nesne önce hazır olmalı.
Üç adım:
- Component scan —
@Component,@Service,@Repository,@Controllerişaretli sınıfları bul. - Çözümleme — her 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 →’in neye ihtiyacı olduğunu çıkar, bağımlılık grafiğini oluştur.
- Oluşturma ve enjeksiyon — bağımlılığı olmayanlardan başlayarak yukarı doğru nesneleri üret.
Kurulum sırası, sınıfları yazdığın sıra değil: önce kimseye ihtiyacı olmayanlar, en son en çok şeye ihtiyacı olanlar kurulur.
Kafam karıştı, daha basit anlat
Spring önce bütün parçaların listesini çıkarır, sonra kimin kime ihtiyacı olduğuna bakar. Kimseye ihtiyacı olmayanı önce kurar, en çok şeye ihtiyacı olanı en son.
Dependency injection'ın temel fikri nedir?
Kendin izle
Varsayılan ayarda grafikte kasıtlı bir döngü var: OrderService → PaymentClient → OrderService.
Spring container — component scan, çözümleme ve enjeksiyon
Tohum 2OrderControllerbulunmadıihtiyacı: OrderService
OrderServicebulunmadıihtiyacı: PaymentClient, OrderRepository
PaymentClientbulunmadıihtiyacı: RestClientConfig, OrderService
OrderRepositorybulunmadıihtiyacı: DataSource
RestClientConfigbulunmadıDataSourcebulunmadı
Şu an ne oldu?
Component scan — 0/6
Spring, ana sınıfın paketinden başlayarak @Component, @Service, @Repository ve @Controller işaretli sınıfları arıyor. Henüz hiçbir nesne oluşturulmadı.
Aklında kalsın: @SpringBootApplication kendi paketini ve altındakileri tarar. Bean’in bulunamamasının en sık sebebi, sınıfın bu paket ağacının dışında kalmasıdır.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
İki ayarı karşılaştır — bu dersin tamamı bu karşılaştırmada:
constructor+ döngü açık — container hiç başlayamıyor.BeanCurrentlyInCreationExceptionve döngü zinciri ekrana basılıyor.field+ döngü açık — container başlıyor. Bean’ler önce boş kurucuyla oluşturuluyor, sonra alanlar dolduruluyor. Döngü hâlâ orada, sadece görünmüyor.
Sonra:
- Döngüyü kapat,
constructor’da kal. Oluşturma sırasına bak:RestClientConfigveDataSourceönce,OrderControlleren son. Yapraklardan köke. @Lazykutusunu işaretle. Döngü açık ama container başlıyor: Spring araya bir 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 → koyuyor ve gerçek bean’i ilk kullanımda çözüyor.
Bu Spring uygulaması açılışta ne yazdırır?
Constructor injection neden öneriliyor
| Constructor | Field (@Autowired alan) | |
|---|---|---|
Alan final olabilir mi | Evet | Hayır |
| Nesne oluştuğu anda geçerli mi | Evet | Hayır — alanlar bir süre null |
| Döngüsel bağımlılık | Açılışta patlar | Sessizce çözülür |
| Spring’siz test | new OrderService(mock) | Reflection gerekir |
| Çok fazla bağımlılık | Kurucu şişer — görünür uyarı | Alan eklemek bedava — uyarı yok |
Son satır gözden kaçar ama önemlidir: sekiz parametreli bir kurucu çirkindir ve sınıfın çok iş yaptığını görünür kılar. Sekiz @Autowired alan aynı şeyi söyler ama kimse fark etmez.
Aşağıdaki örnek bir bankanın para transferi servisinden: aynı arayüzün iki uygulaması (FAST ve EFT) sırayla denenir, dolandırıcılık kontrolü profile göre değişir ve servis Spring’siz test edilir.
Derinleş · Bankada para transferi: bağımlılıklar nereden geliyor? 6 dosya · ~171 satır · ilk okumada atlayabilirsin
Kafam karıştı, daha basit anlat
İhtiyaçlarını constructor’da iste: sınıf eksik parçayla hiç kurulamaz. Testte de Spring’e gerek kalmaz, parçaları kendin verirsin.
Constructor injection'da `@Autowired` yazmak zorunlu mu?
Constructor injection ile field injection arasındaki en önemli pratik fark nedir?
Döngüsel bağımlılığın doğru çözümü. @Lazy ve field injection döngüyü çözmez, erteler. Döngü neredeyse her zaman bir tasarım sorununun belirtisidir:
- Sorumluluğu ayır. İki servisin birbirine ihtiyaç duyması, genelde ikisine de ait olan üçüncü bir kavram olduğu anlamına gelir. Onu ayrı bir sınıfa çıkar.
- Olayla gevşet.
A,B’yi doğrudan çağırmak yerine “şu oldu” diye bir olay yayınlar,Bde onu dinler. - Arayüzü daralt.
A’nınB’den ihtiyaç duyduğu tek metotsa, o metodu ayrı ve küçük bir arayüze taşı.
Döngüye izin veren ayar· istersen atla
Spring Boot 2.6’dan beri döngüsel bağımlılıklar varsayılan olarak yasaktır. spring.main.allow-circular-references=true ile açılabilir, ama bu bir çözüm değil, bir erteleme düğmesidir.
Bean scope’ları
Varsayılan singleton: container başına bir örnek.
| Scope | Kaç örnek |
|---|---|
singleton (varsayılan) | Container başına bir tane |
prototype | Her enjeksiyon/istekte yeni |
request | Her HTTP isteğinde bir tane |
session | Her oturumda bir tane |
Singleton içine prototype enjekte etmek· istersen atla
Bir singleton scopeVarsayılan bean kapsamı: konteyner başına tek örnek, tüm enjeksiyon noktaları aynı nesneyi paylaşır.Uygulama başına değil, konteyner başına tektir. Paylaşıldığı için singleton bir bean'e istek verisi yazmak, o veriyi tüm kullanıcılara dağıtmak demektir.Sözlükte gör →’a prototype bean enjekte edersen, prototype yalnızca bir kez oluşturulur — singleton bir kez kurulduğu için. Her çağrıda yeni örnek istiyorsan ObjectProvider<T> kullan ve ihtiyaç anında iste.
Her bean’den tek bir tane olduğu için bütün istekler aynı nesneyi kullanır. Bu yüzden bean’ler durumsuz (stateless) olmalı: bir alana istek verisi yazarsan, iki kullanıcının verisi birbirine karışır.
Kafam karıştı, daha basit anlat
Singleton bir kez kurulur ve hep aynı kalır. İçine koyduğun her şey de o tek kurulumda bir kez seçilir. Her seferinde yenisini istiyorsan, onu her seferinde iste.
Her ifadeyi doğru enjeksiyon stiline yerleştir.
Tuzaklar
En sık üç sebep:
- Sınıf tarama ağacının dışında.
@SpringBootApplicationkendi paketini ve altındakileri tarar; kardeş bir paketteki bean bulunmaz. - Anotasyon yok. Sınıf
@Componentailesinden biriyle işaretlenmemiş, veya@Beanmetodu bir@Configurationsınıfında değil. - Aynı tipten birden fazla aday.
NoUniqueBeanDefinitionException—@Primaryveya@Qualifiergerekir.
Kendini sına
Dependency injection'da sınıf ihtiyaç duyduğu nesneleri kendisi yaratır.
İki bean birbirine bağımlı (A → B → A). Constructor injection ile ne olur?
Aklında kalacak üç şey
- 1 Bağımlılığı sınıfın kendisi oluşturmaz, dışarıdan verilir. Buna Inversion of Control denir; Spring bunu senin yerine yapar.
- 2 Constructor injection'ın üç artısı var: nesne doğduğu anda hazırdır, alanlar final olabilir ve döngüsel bağımlılık açılışta patlar.
- 3 Field injection döngüyü çözmez, saklar. Üstelik sınıfı Spring olmadan test etmeyi zorlaştırır.
5 kart sonraki derste seni bekliyor
Bu dersin üstüne kurulanlar
Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.
- Spring Boot EkosistemiSpring MVC — Bir İstek Controller'a Nasıl Ulaşır?Security bir isteği 401 ile geri çevirdi. Senin yazdığın audit interceptor'ı bu isteği kaydeder mi?Derse git
- Spring Boot EkosistemiJPA N+1 ProblemiBir liste çektin, sonra her elemanın detayına baktın. Kaç sorgu çalıştı?Derse git
- Spring Boot EkosistemiSpring Security Filter ChainBir istek controller'a varmadan önce kaç turnikeden geçer?Derse git
- Spring Boot EkosistemiAuto-configuration — Bu Bean Nereden Geldi?Hiç yazmadığın bir DataSource nasıl oldu da hazır geldi?Derse git