Yapılandırma Yöntemleri ve Stereotype Anotasyonları
Container'ın bean'leri yönettiğini biliyoruz. Peki container'a "hangi bean'ler var?" diye nasıl söyleriz? Spring bunun için birkaç yol sunar ve modern projelerde genelde ikisini birlikte kullanırız: kendi sınıflarımız…
Yapılandırma Yöntemleri ve Stereotype Anotasyonları
Container'ın bean'leri yönettiğini biliyoruz. Peki container'a "hangi bean'ler var?" diye nasıl
söyleriz? Spring bunun için birkaç yol sunar ve modern projelerde genelde ikisini birlikte
kullanırız: kendi sınıflarımızı stereotype anotasyonlarıyla işaretler, kontrol etmediğimiz
sınıfları ise Java config ile bean yaparız. Bu bölümde bean tanımlamanın yollarını, değer
enjeksiyonunu (@Value) ve katmanlı mimarinin stereotype'larını işliyoruz.
Yapılandırma yöntemleri
Java tabanlı yapılandırma (@Configuration + @Bean)
Bir @Configuration sınıfında @Bean metotları yazarsın; her metot bir bean üretir. Oluşturmayı
tam kontrol edersin — bu yüzden özellikle kaynak kodunu değiştiremediğin üçüncü-parti
sınıflar için idealdir:
@Configuration class JavaConfig { @Bean PdfUretici pdfUretici() { return new PdfUretici(); } @Bean RaporServisi raporServisi(PdfUretici u) { return new RaporServisi(u); } }
Anotasyon tabanlı + bileşen tarama (@Component + @ComponentScan)
Kendi sınıflarını @Component (veya türevleri) ile işaretlersin; @ComponentScan belirtilen
paketi tarayıp bu sınıfları otomatik bean yapar. En yaygın yöntemdir:
@Service class RaporServisi { ... } // @ComponentScan otomatik bulur
XML tabanlı yapılandırma
Spring'in en eski yöntemidir (<bean .../>). Bugün yeni projelerde tercih edilmez ama eski/legacy
projelerde hâlâ karşına çıkabilir.
Örnek 1 (./Ornek1.java) aynı bean'i hem Java config hem stereotype ile tanımlayıp ikisinin de
aynı sonucu verdiğini gösterir.
Örneklerde tek dosya senaryosu nedeniyle
@ComponentScanyerinectx.register(...)kullanıyoruz; davranış aynıdır (sınıf bir bean tanımına dönüşür).
@Value ile değer enjeksiyonu
Bean'lere yalnızca başka bean'ler değil, dış yapılandırma değerleri de enjekte edilir.
@Value bunu sağlar:
@Value("${uygulama.ad}") String ad; // property'den @Value("${uygulama.dil:tr}") String dil; // yoksa varsayılan "tr" @Value("#{ 8 * 1024 }") int maxKb; // SpEL ifadesi
${...} bir property placeholder'dır (application.properties, ortam değişkeni, JVM -D
parametresinden beslenir; : ile varsayılan verilir). #{...} ise SpEL (Spring Expression
Language) ifadesidir ve çalışma anında hesaplanır. Örnek 2 (./Ornek2.java) her ikisini de
gösterir (SpEL ile sistem özelliklerine erişim dahil).
Stereotype anotasyonları ve katmanlı mimari
Spring, @Component'in anlamsal türevlerini sunar. Hepsi temelde aynıdır (container için birer
bean), ama hangi katmana ait olduklarını belirtir ve bazıları katmana özel davranış ekler:
| Anotasyon | Katman | Ek davranış |
|---|---|---|
@Component | Genel | Taban; özel anlamı yok |
@Service | İş mantığı | Anlamsal (servis katmanı) |
@Repository | Veri erişim | İstisnaları DataAccessException'a otomatik çevirir |
@Controller | Web sunum | İstek eşleme için (REST'te @RestController) |
Tipik kurumsal akış üç katmandır: Controller → Service → Repository. Her katmanın tek bir
sorumluluğu vardır; bu, kodu okunur, test edilebilir ve sürdürülebilir kılar. Örnek 3
(./Ornek3.java) bu akışı stereotype'larla kurar ve hepsinin birer bean olduğunu gösterir.
@Repository'nin sessiz bir süper gücü vardır: veritabanı sürücüsünün fırlattığı tekniğe özel istisnaları, Spring'in tutarlıDataAccessExceptionhiyerarşisine çevirir — böylece iş kodun JDBC/JPA ayrıntısından soyutlanır.
@Primary ve @Qualifier ile bean seçimi
Önceki bölümde gördüğümüz gibi, aynı tipte birden çok bean olduğunda @Primary (varsayılan
tercih) ve @Qualifier (açık seçim) belirsizliği çözer. Bunlar yapılandırmanın ayrılmaz parçasıdır.
Özet
Bean tanımlamanın yollarını (Java config @Bean, stereotype + @ComponentScan, eski XML),
@Value ile property/SpEL enjeksiyonunu ve katmanlı mimarinin stereotype anotasyonlarını
(@Component/@Service/@Repository/@Controller) öğrendik. Container'ın temelini artık
tümüyle biliyorsun. Sırada, iş mantığını "kesişen ilgilerden" (loglama, güvenlik, ölçüm) ayıran
güçlü bir teknik: AOP.
▶ Kod Örnekleri(3)
Ornek1
ortam gerekir1// Ornek1: Bean tanımlamanın iki yolu — Java config (@Bean) vs stereotype (@Component).
2// Çalıştırma: java Ornek1.java
3import org.springframework.context.annotation.AnnotationConfigApplicationContext;
4import org.springframework.context.annotation.Bean;
5import org.springframework.context.annotation.Configuration;
6import org.springframework.stereotype.Service;
7
8public class Ornek1 {
9
10 public static void main(String[] args) {
11 // YOL 1: Java tabanlı yapılandırma — @Configuration sınıfında @Bean metotları.
12 // Bean oluşturmayı SEN kontrol edersin (3. parti sınıflar için idealdir).
13 System.out.println("--- Java config (@Bean) ---");
14 var ctx1 = new AnnotationConfigApplicationContext(JavaConfig.class);
15 ctx1.getBean(RaporServisi.class).rapor();
16 ctx1.close();
17
18 // YOL 2: Stereotype + bileşen tarama — sınıfı @Component/@Service ile işaretlersin,
19 // Spring onu otomatik bulur. (Tek dosyada register ile; gerçekte @ComponentScan.)
20 System.out.println("\n--- Stereotype (@Service) ---");
21 var ctx2 = new AnnotationConfigApplicationContext();
22 ctx2.register(PdfUretici.class, RaporServisi2.class);
23 ctx2.refresh();
24 ctx2.getBean(RaporServisi2.class).rapor();
25 ctx2.close();
26
27 System.out.println("""
28
29 --- Hangisi ne zaman? ---
30 @Bean (Java config): kaynak kodunu DEĞİŞTİREMEDİĞİN sınıfları (3. parti) bean yapmak;
31 oluşturmayı tam kontrol etmek istediğinde.
32 @Component/@Service : kendi yazdığın sınıflar; @ComponentScan ile otomatik bulunur (en yaygın).
33 Eski projelerde bir de XML tabanlı yapılandırma vardır; bugün tercih edilmez.""");
34 }
35}
36
37// --- YOL 1: Java config ---
38class PdfUretici { String uret() { return "PDF raporu üretildi"; } }
39class RaporServisi {
40 private final PdfUretici uretici;
41 RaporServisi(PdfUretici uretici) { this.uretici = uretici; }
42 void rapor() { System.out.println(" " + uretici.uret()); }
43}
44
45@Configuration
46class JavaConfig {
47 @Bean PdfUretici pdfUretici() { return new PdfUretici(); }
48 @Bean RaporServisi raporServisi(PdfUretici u) { return new RaporServisi(u); }
49}
50
51// --- YOL 2: Stereotype ---
52@Service
53class RaporServisi2 {
54 private final PdfUretici uretici;
55 RaporServisi2(PdfUretici uretici) { this.uretici = uretici; }
56 void rapor() { System.out.println(" " + uretici.uret() + " (stereotype ile)"); }
57}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: @Value ile değer/property enjeksiyonu ve SpEL.
2// Çalıştırma: java Ornek2.java
3import org.springframework.beans.factory.annotation.Value;
4import org.springframework.context.annotation.AnnotationConfigApplicationContext;
5import org.springframework.context.annotation.Bean;
6import org.springframework.context.annotation.Configuration;
7import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
8import org.springframework.core.env.Environment;
9import org.springframework.stereotype.Component;
10
11import java.util.Properties;
12
13public class Ornek2 {
14
15 public static void main(String[] args) {
16 var ctx = new AnnotationConfigApplicationContext(AyarConfig.class);
17 ctx.getBean(AyarYazici.class).yaz();
18 ctx.close();
19
20 System.out.println("""
21
22 --- @Value nereden beslenir? ---
23 - application.properties / application.yml dosyalarından,
24 - ortam değişkenleri ve JVM -D parametrelerinden,
25 - varsayılan değerlerle: ${anahtar:varsayilan}
26 SpEL ifadeleri için: @Value("#{...}") (ör. matematik, başka bean'e erişim).""");
27 }
28}
29
30@Configuration
31class AyarConfig {
32 // Bu örnekte property'leri programatik veriyoruz; gerçekte application.properties'ten gelir.
33 @Bean
34 static PropertySourcesPlaceholderConfigurer pspc() {
35 var c = new PropertySourcesPlaceholderConfigurer();
36 var p = new Properties();
37 p.setProperty("uygulama.ad", "Eğitim Portalı");
38 p.setProperty("uygulama.surum", "1.0");
39 c.setProperties(p);
40 return c;
41 }
42
43 @Bean AyarYazici ayarYazici() { return new AyarYazici(); }
44}
45
46@Component
47class AyarYazici {
48 // ${...}: property'den enjekte; :varsayilan ile yedek değer.
49 @Value("${uygulama.ad}")
50 private String ad;
51
52 @Value("${uygulama.surum}")
53 private String surum;
54
55 @Value("${uygulama.dil:tr}") // tanımlı değil -> varsayılan "tr"
56 private String dil;
57
58 // #{...}: SpEL ifadesi (çalışma anında hesaplanır).
59 @Value("#{ 8 * 1024 }")
60 private int maxBoyutKb;
61
62 // SpEL ile sistem özelliklerine de erişilebilir (çalışan JVM sürümü).
63 @Value("#{ systemProperties['java.specification.version'] }")
64 private String javaSurumu;
65
66 void yaz() {
67 System.out.println("uygulama.ad = " + ad);
68 System.out.println("uygulama.surum = " + surum);
69 System.out.println("uygulama.dil = " + dil + " (varsayılan kullanıldı)");
70 System.out.println("maxBoyutKb = " + maxBoyutKb + " (SpEL ile hesaplandı)");
71 System.out.println("javaSurumu = " + javaSurumu + " (SpEL + systemProperties)");
72 }
73}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: Stereotype anotasyonları ve katmanlı mimari (@Controller/@Service/@Repository).
2// Hepsi aslında @Component'tir; fark ANLAMSAL (semantic) ve katmana özel davranışlardır.
3// Çalıştırma: java Ornek3.java
4import org.springframework.context.annotation.AnnotationConfigApplicationContext;
5import org.springframework.stereotype.Controller;
6import org.springframework.stereotype.Repository;
7import org.springframework.stereotype.Service;
8
9public class Ornek3 {
10
11 public static void main(String[] args) {
12 var ctx = new AnnotationConfigApplicationContext();
13 ctx.register(UrunController.class, UrunService.class, UrunRepository.class);
14 ctx.refresh();
15
16 // Tipik akış: Controller -> Service -> Repository (her katmanın tek bir sorumluluğu var).
17 ctx.getBean(UrunController.class).istekGeldi("Klavye");
18
19 // Hepsi @Component olduğundan birer bean'dir:
20 System.out.println("\nKatman bean'leri:");
21 for (String ad : new String[]{"urunController", "urunService", "urunRepository"}) {
22 System.out.println(" " + ad + " -> " + ctx.getBean(ad).getClass().getSimpleName());
23 }
24 ctx.close();
25
26 System.out.println("""
27
28 --- Stereotype anotasyonları ---
29 @Component : genel amaçlı bean (taban).
30 @Service : iş mantığı katmanı (anlamsal; @Component ile aynı temel).
31 @Repository : veri erişim katmanı + DataAccessException'a OTOMATİK çeviri.
32 @Controller : web sunum katmanı (@RestController bunun REST türevidir).
33 Hepsi @ComponentScan ile otomatik bulunur; ayrım okunabilirlik ve katmana özel davranış içindir.""");
34 }
35}
36
37@Controller // sunum katmanı (web'de istekleri karşılar)
38class UrunController {
39 private final UrunService service;
40 UrunController(UrunService service) { this.service = service; }
41 void istekGeldi(String urun) {
42 System.out.println("[Controller] istek alındı: " + urun);
43 service.kaydet(urun);
44 }
45}
46
47@Service // iş mantığı katmanı
48class UrunService {
49 private final UrunRepository repo;
50 UrunService(UrunRepository repo) { this.repo = repo; }
51 void kaydet(String urun) {
52 System.out.println("[Service] iş kuralları uygulandı");
53 repo.insert(urun);
54 }
55}
56
57@Repository // veri erişim katmanı
58class UrunRepository {
59 void insert(String urun) { System.out.println("[Repository] veritabanına yazıldı: " + urun); }
60}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.