Spring Nedir ve IoC Felsefesi
Spring'i öğrenmenin en iyi yolu, onun hangi sorunu çözdüğünü anlamaktır. Çünkü Spring bir "sihir" değil; uzun yıllar boyunca kurumsal Java uygulamalarında tekrar tekrar karşılaşılan bir acıya verilmiş, çok zarif bir c…
Spring Nedir ve IoC Felsefesi
Spring'i öğrenmenin en iyi yolu, onun hangi sorunu çözdüğünü anlamaktır. Çünkü Spring bir "sihir" değil; uzun yıllar boyunca kurumsal Java uygulamalarında tekrar tekrar karşılaşılan bir acıya verilmiş, çok zarif bir cevaptır. Bu bölümde önce o acıyı kendi gözünle göreceğiz, sonra elle nasıl hafifletilebileceğini, en sonunda da Spring'in onu nasıl kökünden çözdüğünü adım adım izleyeceğiz. Bu üç adım, bütün Spring'in temelidir.
Spring nedir?
Spring, Java için bir uygulama çatısıdır (framework). Çekirdeğinde, nesnelerin yaşam döngüsünü ve birbirleriyle olan bağımlılıklarını yöneten bir IoC container bulunur. Spring tek bir kütüphane değil, modüler bir ekosistemdir: çekirdek (Core/Context), web (MVC), veri (Data/JPA), güvenlik (Security), AOP ve daha fazlası. Hepsinin altında aynı felsefe yatar: senin işin iş mantığını yazmaktır; "su tesisatını" (nesne oluşturma, bağlama, yönetme) Spring üstlenir.
Asıl sorun: sıkı bağlılık (tight coupling)
Bir sınıf, ihtiyaç duyduğu diğer sınıfları kendi içinde new ile yaratırsa ne olur? Örnek 1
(./Ornek1.java) tam da bunu gösterir: bir SiparisServisi, MySqlDepo ve EpostaGonderici
nesnelerini içeride oluşturur.
class SiparisServisi { private final MySqlDepo depo = new MySqlDepo(); // sıkıca bağlı! private final EpostaGonderici gonderici = new EpostaGonderici(); }
Bu kod çalışır, ama kırılgandır:
- Değiştirilemez: Veritabanını PostgreSQL'e ya da bildirimi SMS'e çevirmek için
SiparisServisisınıfının içini değiştirmen gerekir. - Test edilemez: Birim testinde gerçek e-posta/veritabanı devreye girer; sahte (mock) bağımlılık veremezsin.
- Yeniden kullanılamaz: Servis, somut sınıflara mıhlanmıştır.
Bu, kurumsal yazılımın klasik kâbusudur: her şey her şeye doğrudan bağlı, küçük bir değişiklik zincirleme kırılmalara yol açar.
İlk adım: arayüzler + elle enjeksiyon
Çözümün ilk yarısı, OOP bölümünde öğrendiğin soyutlamadır. Somut sınıflara değil
arayüzlere (Depo, Bildirimci) bağlanır ve bağımlılıkları içeride yaratmak yerine
dışarıdan (constructor ile) alırız. Buna Dependency Injection (DI) denir:
class SiparisServisi { SiparisServisi(Depo depo, Bildirimci bildirimci) { ... } // dışarıdan verilir }
Örnek 2 (./Ornek2.java) bunu uygular. Artık uygulamayı değiştirmek için servisi değil, yalnızca
kurulum satırını değiştiriyoruz; testte sahte bağımlılık vermek bir lambda kadar kolay. Ama
bir bedeli var: tüm nesneleri ve aralarındaki bağlantıları elle kurmak zorundayız. Uygulama
büyüdükçe bu "kurulum kodu" devasa ve hataya açık bir hale gelir.
Çözüm: IoC ve Spring container
İşte Spring'in devreye girdiği yer burası. IoC (Inversion of Control — Kontrolün Tersine Çevrilmesi) fikri şudur: nesneleri oluşturma ve bağlama kontrolünü sen değil, container üstlenir. Sen yalnızca "hangi bean'ler var ve nasıl kurulur" diye tarif edersin; gerisini Spring yapar.
var ctx = new AnnotationConfigApplicationContext(UygulamaConfig.class); SiparisServisi servis = ctx.getBean(SiparisServisi.class); // hazır, bağımlılıkları enjekte edilmiş
Örnek 3 (./Ornek3.java) Örnek 2'deki aynı iş mantığını alır ama nesne kurulumunu Spring'e
devreder. @Configuration sınıfı container'a bean tariflerini verir; container, siparisServisi
bean'ine depo ve bildirimci bean'lerini otomatik enjekte eder. Sen artık new yazmaz,
bağlantıları elle kurmazsın.
Dependency Injection, IoC'nin en bilinen uygulama biçimidir. "Kontrolün tersine çevrilmesi" daha geniş bir ilkedir; DI ise onu bağımlılıklar özelinde hayata geçirir.
Spring container ve bean'ler
Birkaç temel terim:
- Bean: Spring container'ın oluşturduğu ve yönettiği nesne. (Senin
SiparisServisi'n bir bean'dir.) - Container / ApplicationContext: Bean'leri oluşturan, bağlayan ve yaşam döngülerini yöneten
Spring'in kalbi.
ApplicationContext,BeanFactory'nin zengin halefidir (olay yayını, uluslararasılaştırma, kaynak yükleme gibi ek yeteneklerle). - Configuration metadata: Container'a "hangi bean'ler var?" diye söyleyen tarif (Java config, anotasyonlar veya eski XML).
Spring ekosistemine kısa bakış
- Core / Context: IoC container, DI, bean yönetimi (bu bölümün konusu).
- AOP: Kesişen ilgileri (loglama, güvenlik, transaction) iş mantığından ayırma.
- Data Access / JDBC / ORM / Data JPA: Veritabanı erişimini kolaylaştırma.
- Web / MVC: HTTP üzerinden çalışan uygulamalar ve REST API'ler.
- Security: Kimlik doğrulama ve yetkilendirme.
Bu eğitimde önce çekirdeği (IoC/DI, bean'ler, yapılandırma, AOP) derinlemesine işleyeceğiz; ardından Spring Boot bölümünde tüm bunların üretim uygulamalarına nasıl dönüştüğünü göreceğiz.
Özet
Spring'in var oluş sebebini bizzat yaşayarak gördük: sıkı bağlılık problemini (Örnek 1), arayüzler + elle DI ile kısmi çözümü (Örnek 2) ve Spring IoC container ile tam çözümü (Örnek 3). Çıkarım net: bağımlılıkları kendin yaratma; container onları senin için oluşturup enjekte etsin. Bu felsefe, bundan sonraki her Spring konusunun altında yatıyor. Sırada, bu enjeksiyonun nasıl yapıldığına yakından bakıyoruz: Dependency Injection türleri.
▶ Kod Örnekleri(3)
Ornek1
çalıştırılabilir1// Ornek1: PROBLEM — sıkı bağlılık (tight coupling). Spring YOK; saf Java.
2// Bu kod ÇALIŞIR ama esnek değildir: bir sınıf, bağımlılıklarını kendi içinde "new" ile yaratır.
3// Çalıştırma: java Ornek1.java
4public class Ornek1 {
5
6 public static void main(String[] args) {
7 // Servisi oluşturuyoruz ama aslında neye bağımlı olduğunu DIŞARIDAN göremiyoruz.
8 // Bağımlılıklar sınıfın İÇİNDE gizli; bu, değiştirmeyi ve test etmeyi zorlaştırır.
9 SiparisServisi servis = new SiparisServisi();
10 servis.siparisVer("Klavye");
11
12 System.out.println("""
13
14 --- Bu tasarımın sorunları ---
15 1) SiparisServisi, MySqlDepo ve EpostaGonderici'ye SIKICA bağlı.
16 2) Depoyu PostgreSQL'e ya da bildirimi SMS'e çevirmek için SINIFI değiştirmek gerekir.
17 3) Test ederken sahte (mock) bağımlılık veremeyiz; gerçek e-posta/DB devreye girer.
18 Çözüm: bağımlılıkları içeride 'new'lemek yerine DIŞARIDAN vermek (sonraki örnekler).""");
19 }
20}
21
22// Somut bağımlılıklar
23class MySqlDepo {
24 void kaydet(String kayit) { System.out.println(" [MySQL] kaydedildi: " + kayit); }
25}
26class EpostaGonderici {
27 void gonder(String mesaj) { System.out.println(" [e-posta] " + mesaj); }
28}
29
30// SORUN: Servis, somut sınıfları KENDİSİ yaratıyor (new). Bu, sıkı bağlılıktır.
31class SiparisServisi {
32 private final MySqlDepo depo = new MySqlDepo(); // <-- sıkıca bağlı
33 private final EpostaGonderici gonderici = new EpostaGonderici(); // <-- sıkıca bağlı
34
35 void siparisVer(String urun) {
36 depo.kaydet(urun);
37 gonderici.gonder("Siparişiniz alındı: " + urun);
38 }
39}Ornek2
çalıştırılabilir1// Ornek2: KISMİ ÇÖZÜM — arayüzler + ELLE bağımlılık enjeksiyonu. Hâlâ Spring YOK.
2// Bağımlılıkları içeride new'lemek yerine, dışarıdan (constructor ile) veriyoruz.
3// Çalıştırma: java Ornek2.java
4public class Ornek2 {
5
6 public static void main(String[] args) {
7 // "Composition root": nesneleri burada kurar ve birbirine bağlarız.
8 // Artık hangi uygulamayı seçeceğimize DIŞARIDAN karar veriyoruz.
9 Depo depo = new PostgresDepo(); // istersek MySqlDepo da verebilirdik
10 Bildirimci bildirimci = new SmsBildirimci(); // istersek e-posta da verebilirdik
11
12 SiparisServisi servis = new SiparisServisi(depo, bildirimci);
13 servis.siparisVer("Mouse");
14
15 // Test için sahte (mock) bağımlılık vermek artık çok kolay:
16 SiparisServisi testServisi = new SiparisServisi(
17 kayit -> System.out.println(" [sahte depo] " + kayit),
18 mesaj -> System.out.println(" [sahte bildirim] " + mesaj));
19 System.out.println("\nTest senaryosu (sahte bağımlılıklarla):");
20 testServisi.siparisVer("Monitör");
21
22 System.out.println("""
23
24 --- Kazandığımız esneklik ---
25 Servis artık ARAYÜZLERE bağlı (Depo, Bildirimci); somut sınıfa değil.
26 Uygulamayı değiştirmek için SERVİSİ değil, sadece kurulum satırını değiştiriyoruz.
27 ANCAK: tüm nesneleri ve bağlantıları HÂLÂ elle yönetmek zorundayız.
28 İşte bu 'elle yönetimi' Spring'in IoC container'ı bizim için yapar (Örnek 3).""");
29 }
30}
31
32// Soyutlamalar (sözleşmeler)
33interface Depo { void kaydet(String kayit); }
34interface Bildirimci { void gonder(String mesaj); }
35
36class PostgresDepo implements Depo {
37 public void kaydet(String k) { System.out.println(" [PostgreSQL] kaydedildi: " + k); }
38}
39class SmsBildirimci implements Bildirimci {
40 public void gonder(String m) { System.out.println(" [SMS] " + m); }
41}
42
43// Bağımlılıklar DIŞARIDAN, constructor ile veriliyor (dependency injection — elle).
44class SiparisServisi {
45 private final Depo depo;
46 private final Bildirimci bildirimci;
47
48 SiparisServisi(Depo depo, Bildirimci bildirimci) {
49 this.depo = depo;
50 this.bildirimci = bildirimci;
51 }
52
53 void siparisVer(String urun) {
54 depo.kaydet(urun);
55 bildirimci.gonder("Siparişiniz alındı: " + urun);
56 }
57}Ornek3
ortam gerekir1// Ornek3: ÇÖZÜM — Spring IoC container. Nesneleri ve bağlantıları artık Spring yönetir.
2// Çalıştırma: java Ornek3.java (Spring classpath portal tarafından sağlanır)
3import org.springframework.context.annotation.AnnotationConfigApplicationContext;
4import org.springframework.context.annotation.Bean;
5import org.springframework.context.annotation.Configuration;
6
7public class Ornek3 {
8
9 public static void main(String[] args) {
10 // IoC = Inversion of Control (kontrolün tersine çevrilmesi):
11 // nesneleri SEN değil, CONTAINER oluşturur ve birbirine bağlar.
12 // Sen sadece "ne istediğini" tarif edersin (yapılandırma); "nasıl"ı Spring halleder.
13 try (var ctx = new AnnotationConfigApplicationContext(UygulamaConfig.class)) {
14
15 // Container'dan hazır, bağımlılıkları enjekte edilmiş servisi isteriz.
16 SiparisServisi servis = ctx.getBean(SiparisServisi.class);
17 servis.siparisVer("Kulaklık");
18
19 // Bean'lerin container tarafından yönetildiğini görelim:
20 System.out.println("\nContainer'daki bazı bean'ler:");
21 for (String ad : new String[]{"depo", "bildirimci", "siparisServisi"}) {
22 System.out.println(" - " + ad + " -> " + ctx.getBean(ad).getClass().getSimpleName());
23 }
24
25 System.out.println("""
26
27 --- Spring ne kazandırdı? ---
28 1) Nesneleri 'new' ile biz oluşturmadık; container oluşturdu.
29 2) Bağımlılıkları (Depo, Bildirimci) container OTOMATİK enjekte etti.
30 3) Uygulamayı değiştirmek için yalnızca YAPILANDIRMAYI değiştirmek yeter.
31 Bu, gevşek bağlılığın ve test edilebilirliğin en üst seviyesidir.""");
32 }
33 }
34}
35
36// Aynı arayüzler ve uygulamalar (Örnek 2 ile aynı iş mantığı).
37interface Depo { void kaydet(String kayit); }
38interface Bildirimci { void gonder(String mesaj); }
39
40class PostgresDepo implements Depo {
41 public void kaydet(String k) { System.out.println(" [PostgreSQL] kaydedildi: " + k); }
42}
43class SmsBildirimci implements Bildirimci {
44 public void gonder(String m) { System.out.println(" [SMS] " + m); }
45}
46
47class SiparisServisi {
48 private final Depo depo;
49 private final Bildirimci bildirimci;
50 SiparisServisi(Depo depo, Bildirimci bildirimci) { this.depo = depo; this.bildirimci = bildirimci; }
51 void siparisVer(String urun) {
52 depo.kaydet(urun);
53 bildirimci.gonder("Siparişiniz alındı: " + urun);
54 }
55}
56
57// Yapılandırma: "hangi bean'ler var ve nasıl kurulur" — container bunu okur.
58@Configuration
59class UygulamaConfig {
60 @Bean Depo depo() { return new PostgresDepo(); }
61 @Bean Bildirimci bildirimci() { return new SmsBildirimci(); }
62
63 // Spring, depo() ve bildirimci() bean'lerini bu metoda OTOMATİK enjekte eder.
64 @Bean SiparisServisi siparisServisi(Depo depo, Bildirimci bildirimci) {
65 return new SiparisServisi(depo, bildirimci);
66 }
67}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.