Dependency Injection (Bağımlılık Enjeksiyonu)
Önceki bölümde IoC felsefesini ve container'ın nesneleri bizim yerimize oluşturup bağladığını gördük. Şimdi bu bağlamanın nasıl yapıldığına yakından bakıyoruz. Dependency Injection (DI), bir nesnenin ihtiyaç duyduğu b…
Dependency Injection (Bağımlılık Enjeksiyonu)
Önceki bölümde IoC felsefesini ve container'ın nesneleri bizim yerimize oluşturup bağladığını gördük. Şimdi bu bağlamanın nasıl yapıldığına yakından bakıyoruz. Dependency Injection (DI), bir nesnenin ihtiyaç duyduğu bağımlılıkların ona dışarıdan verilmesidir. Spring üç farklı enjeksiyon türü sunar — ve hangisini seçtiğin, kodunun kalitesini doğrudan etkiler.
@Autowired ve enjeksiyon noktaları
Spring, bir bean'in bağımlılığını çözmek için @Autowired anotasyonunu kullanır (constructor,
setter veya alan üzerinde). Bu, container'a "buraya uygun tipte bir bean enjekte et" der.
Modern Spring'de tek constructor varsa @Autowired yazmaya bile gerek yoktur; Spring onu
otomatik kullanır.
Not: Örneklerde, tek dosyalık çalışma senaryosu nedeniyle bean'leri
ctx.register(...)ile doğrudan kaydediyoruz. Gerçek uygulamada bu sınıflar@ComponentScanile otomatik bulunur (bir sonraki bölümde göreceğiz).
1) Constructor injection (önerilen)
Bağımlılığı constructor parametresi olarak almaktır ve Spring'in önerdiği yoldur:
@Service class UrunServisi { private final UrunDeposu depo; // final olabilir! UrunServisi(UrunDeposu depo) { this.depo = depo; } }
Avantajları (Örnek 1, ./Ornek1.java):
- Değişmezlik: Alanlar
finalolabilir → güvenli, durumu değişmeyen nesne. - Açık ve zorunlu bağımlılıklar: Nesne, bağımlılıkları olmadan var olamaz; eksik kurulum
derleme/başlatma anında belli olur, çalışma anında
NullPointerExceptionile patlamaz. - Test edilebilirlik: Testte bağımlılığı doğrudan constructor'a verirsin — Spring'e bile ihtiyaç kalmaz.
2) Setter injection
Bağımlılık, bir setter metodu aracılığıyla, nesne kurulduktan sonra enjekte edilir:
@Autowired void setEposta(EpostaServisi eposta) { this.eposta = eposta; }
Opsiyonel bağımlılıklar veya çalışma sırasında yeniden yapılandırma gereken durumlar için
uygundur. Dezavantajı: alan final olamaz ve nesne, setter çağrılana kadar yarım kurulu kalır.
3) Field injection
Bağımlılık doğrudan alana (reflection ile) yazılır:
@Autowired private EpostaServisi eposta;
En kısa görünen ama en sorunlu yöntemdir: alan final olamaz, bağımlılık "gizlidir"
(constructor'a bakarak göremezsin), ve Spring olmadan birim testi yapmak zorlaşır (reflection
gerekir). Örnek 2 (./Ornek2.java) setter ve field injection'ı yan yana gösterir.
Pratik kural: Zorunlu bağımlılıklar için constructor injection kullan. Field injection'dan kaçın; setter injection'ı yalnızca gerçekten opsiyonel bağımlılıklar için sakla.
Belirsizliği çözmek: @Primary ve @Qualifier
Bir arayüzün (Bildirimci) birden çok uygulaması (e-posta, SMS) bean olarak tanımlıysa, Spring
hangisini enjekte edeceğini bilemez ve hata verir. İki çözüm vardır:
@Primary: "Eşit adaylar arasında varsayılan olarak BENİ seç."@Qualifier("ad"): "Tam olarak şu isimli bean'i ver" — açık ve nokta atışı seçim.
@Component @Primary class EpostaBildirimci implements Bildirimci { ... } @Component("sms") class SmsBildirimci implements Bildirimci { ... } AcilServis(@Qualifier("sms") Bildirimci b) { ... } // açıkça SMS
Örnek 3 (./Ornek3.java) bir bildirim senaryosunda her ikisini de gösterir: varsayılan servis
@Primary olan e-postayı, acil servis ise @Qualifier ile SMS'i alır.
Özet
Dependency Injection'ın üç türünü gördük: constructor (önerilen — final, zorunlu, test
edilebilir), setter (opsiyonel bağımlılıklar) ve field (kaçınılması gereken). Aynı tipte birden
çok bean olduğunda belirsizliği @Primary ve @Qualifier ile çözdük. Bağımlılıkların nasıl
verildiğini öğrendiğimize göre, sırada bu bean'lerin container içindeki yaşam döngüsü ve
kapsamları (scope) var.
▶ Kod Örnekleri(3)
Ornek1
ortam gerekir1// Ornek1: Constructor injection — Spring'in ÖNERDİĞİ enjeksiyon türü.
2// @Service/@Repository sınıflarını container'a kaydedip otomatik bağlamasını izliyoruz.
3// Çalıştırma: java Ornek1.java (Spring classpath portal tarafından sağlanır)
4import org.springframework.context.annotation.AnnotationConfigApplicationContext;
5import org.springframework.stereotype.Repository;
6import org.springframework.stereotype.Service;
7
8public class Ornek1 {
9
10 public static void main(String[] args) {
11 // @ComponentScan yerine sınıfları doğrudan kaydediyoruz (tek dosya senaryosu).
12 // Gerçek uygulamada @ComponentScan bu sınıfları otomatik bulur.
13 var ctx = new AnnotationConfigApplicationContext();
14 ctx.register(UrunServisi.class, UrunDeposu.class);
15 ctx.refresh();
16
17 UrunServisi servis = ctx.getBean(UrunServisi.class);
18 servis.urunEkle("Klavye");
19 servis.listele();
20
21 ctx.close();
22
23 System.out.println("""
24
25 --- Neden constructor injection? ---
26 1) Alanlar 'final' olabilir -> değişmez (immutable), güvenli nesne.
27 2) Zorunlu bağımlılıklar AÇIKTIR: nesne, eksiksiz kurulmadan var olamaz.
28 3) Test kolaydır: testte bağımlılığı doğrudan constructor'a verirsin (Spring bile gerekmez).
29 Spring 4.3+ ile tek constructor varsa @Autowired yazmaya bile gerek yoktur.""");
30 }
31}
32
33@Repository
34class UrunDeposu {
35 private final java.util.List<String> kayitlar = new java.util.ArrayList<>();
36 void kaydet(String urun) { kayitlar.add(urun); }
37 java.util.List<String> hepsi() { return kayitlar; }
38}
39
40@Service
41class UrunServisi {
42 // CONSTRUCTOR INJECTION: bağımlılık final, zorunlu ve açık.
43 private final UrunDeposu depo;
44
45 // Tek constructor olduğu için @Autowired ZORUNLU değil; Spring otomatik enjekte eder.
46 UrunServisi(UrunDeposu depo) {
47 this.depo = depo;
48 System.out.println("UrunServisi kuruldu; depo enjekte edildi: " + (depo != null));
49 }
50
51 void urunEkle(String urun) { depo.kaydet(urun); System.out.println("Eklendi: " + urun); }
52 void listele() { System.out.println("Tüm ürünler: " + depo.hepsi()); }
53}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: Setter ve field injection — alternatifler ve neden constructor kadar iyi olmadıkları.
2// Çalıştırma: java Ornek2.java
3import org.springframework.beans.factory.annotation.Autowired;
4import org.springframework.context.annotation.AnnotationConfigApplicationContext;
5import org.springframework.stereotype.Component;
6import org.springframework.stereotype.Service;
7
8public class Ornek2 {
9
10 public static void main(String[] args) {
11 var ctx = new AnnotationConfigApplicationContext();
12 ctx.register(EpostaServisi.class, SetterServisi.class, FieldServisi.class);
13 ctx.refresh();
14
15 System.out.println("Setter injection:");
16 ctx.getBean(SetterServisi.class).calistir();
17
18 System.out.println("\nField injection:");
19 ctx.getBean(FieldServisi.class).calistir();
20
21 ctx.close();
22
23 System.out.println("""
24
25 --- Karşılaştırma ---
26 Setter injection: bağımlılık SONRADAN değiştirilebilir (opsiyonel bağımlılıklar için uygun)
27 ama alan 'final' olamaz ve nesne yarım kurulu bir an yaşayabilir.
28 Field injection : en kısa görünür AMA en sorunlusudur:
29 - alan 'final' olamaz, gizli bağımlılık (constructor'a bakınca görünmez),
30 - Spring olmadan test etmek zordur (reflection gerekir).
31 ÖNERİ: Zorunlu bağımlılıklar için CONSTRUCTOR injection kullan (Örnek 1).""");
32 }
33}
34
35@Component
36class EpostaServisi {
37 void gonder(String m) { System.out.println(" [e-posta] " + m); }
38}
39
40@Service
41class SetterServisi {
42 private EpostaServisi eposta; // final OLAMAZ
43
44 // SETTER INJECTION: Spring, kurulumdan sonra bu setter'ı çağırıp enjekte eder.
45 @Autowired
46 void setEposta(EpostaServisi eposta) { this.eposta = eposta; }
47
48 void calistir() { eposta.gonder("setter ile enjekte edildi"); }
49}
50
51@Service
52class FieldServisi {
53 // FIELD INJECTION: Spring değeri doğrudan alana (reflection ile) yazar.
54 @Autowired
55 private EpostaServisi eposta; // final OLAMAZ, gizli bağımlılık
56
57 void calistir() { eposta.gonder("field ile enjekte edildi"); }
58}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: Aynı tipte birden çok bean — belirsizliği @Primary ve @Qualifier ile çözmek.
2// Gerçek senaryo: e-posta ve SMS aynı arayüzü uygular; Spring hangisini enjekte etsin?
3// Çalıştırma: java Ornek3.java
4import org.springframework.beans.factory.annotation.Qualifier;
5import org.springframework.context.annotation.AnnotationConfigApplicationContext;
6import org.springframework.context.annotation.Primary;
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(EpostaBildirimci.class, SmsBildirimci.class,
15 VarsayilanServis.class, AcilServis.class);
16 ctx.refresh();
17
18 // VarsayilanServis, @Primary olan bildirimciyi (e-posta) alır.
19 System.out.print("Varsayılan servis -> ");
20 ctx.getBean(VarsayilanServis.class).bildir("Hoş geldiniz");
21
22 // AcilServis, @Qualifier ile AÇIKÇA SMS'i ister.
23 System.out.print("Acil servis -> ");
24 ctx.getBean(AcilServis.class).bildir("Sunucu çöktü!");
25
26 ctx.close();
27
28 System.out.println("""
29
30 --- Belirsizlik (ambiguity) ---
31 Bildirimci tipinde İKİ bean var (e-posta, SMS). Spring hangisini seçeceğini bilemez.
32 Çözüm:
33 @Primary -> "eşit adaylar varsa BENİ seç" (varsayılan tercih).
34 @Qualifier-> "tam olarak ŞU isimli bean'i ver" (açık seçim).""");
35 }
36}
37
38interface Bildirimci { void gonder(String mesaj); }
39
40@Component
41@Primary // birden çok aday olduğunda varsayılan olarak bu seçilir
42class EpostaBildirimci implements Bildirimci {
43 public void gonder(String m) { System.out.println("[e-posta] " + m); }
44}
45
46@Component("sms") // bean adı: "sms"
47class SmsBildirimci implements Bildirimci {
48 public void gonder(String m) { System.out.println("[SMS] " + m); }
49}
50
51@Service
52class VarsayilanServis {
53 private final Bildirimci bildirimci;
54 VarsayilanServis(Bildirimci bildirimci) { this.bildirimci = bildirimci; } // @Primary geldi
55 void bildir(String m) { bildirimci.gonder(m); }
56}
57
58@Service
59class AcilServis {
60 private final Bildirimci bildirimci;
61 // @Qualifier ile tam olarak "sms" bean'ini istiyoruz.
62 AcilServis(@Qualifier("sms") Bildirimci bildirimci) { this.bildirimci = bildirimci; }
63 void bildir(String m) { bildirimci.gonder(m); }
64}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.