Reflection ve Annotations
Şimdiye kadar yazdığın kod, derleme anında neyle çalışacağını biliyordu. Ama Spring, Hibernate, Jackson, JUnit gibi araçlar bir "sihir" yapıyormuş gibi görünür: sınıflarını tanır, nesnelerini oluşturur, alanlarını oku…
Reflection ve Annotations
Şimdiye kadar yazdığın kod, derleme anında neyle çalışacağını biliyordu. Ama Spring,
Hibernate, Jackson, JUnit gibi araçlar bir "sihir" yapıyormuş gibi görünür: sınıflarını tanır,
nesnelerini oluşturur, alanlarını okur, @Autowired/@Entity/@Test gibi etiketlere göre
davranır. Bu sihrin ardında iki mekanizma yatar: reflection (kodu çalışma zamanında
inceleme ve yönlendirme) ve annotations (koda makine tarafından okunabilir meta-veri
ekleme). Bu bölümde ikisini de, bu framework'lerin minyatür versiyonlarını kendi elinle
yazarak öğreneceksin.
Reflection
Reflection, çalışma zamanında bir sınıfın yapısını (alanlar, metotlar, constructor'lar,
anotasyonlar) keşfetmeni ve onlarla etkileşmeni sağlar. Başlangıç noktası Class nesnesidir:
Class<?> c = nesne.getClass(); // bir nesneden Class<?> c2 = Urun.class; // tip adından c.getDeclaredFields(); // alanlar c.getDeclaredMethods(); // metotlar c.getSuperclass(); // üst sınıf
Gerçek kullanım: genel amaçlı serializer
Reflection'ın en yaygın kullanımı, "herhangi bir tip" için çalışan genel araçlardır. Bir
JSON kütüphanesi nesneni nasıl metne çevirir? Alanlarını reflection ile okuyarak. Örnek 1
(./Ornek1.java) tam da bunu yapar: herhangi bir nesneyi alanlarıyla bir Map'e çeviren bir
nesneToMap ve her sınıf için çalışan genel bir toString üreticisi yazar. setAccessible(true)
ile private alanlara bile erişebildiğine dikkat et — bu güç, framework'lerin nesnelerine
nüfuz etmesini sağlar.
Dinamik oluşturma ve çağırma
Reflection sadece okumakla kalmaz; nesne oluşturabilir, metot çağırabilir, alan değiştirebilir — hem de isimler/tipler çalışma zamanında verildiğinde:
Constructor<?> ctor = sinif.getDeclaredConstructor(String.class); Object nesne = ctor.newInstance("değer"); Method m = sinif.getDeclaredMethod("calistir"); m.invoke(nesne);
Annotations
Annotation, koda eklediğimiz meta-veridir: davranışı doğrudan değiştirmez, "bu eleman
şöyledir" bilgisini taşır. Zaten bazılarını kullandın: @Override, @Deprecated,
@FunctionalInterface. Framework'ler ise kendi anotasyonlarını tanımlar ve reflection ile
okuyup davranışa çevirir.
Kendi annotation'ını tanımlarken iki şey kritiktir:
@Retention(RUNTIME): Reflection ile okunabilmesi için çalışma zamanında saklanmalı.@Target: Nereye konabileceği (alan, metot, constructor, sınıf...).
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface NotBlank { String mesaj() default "boş olamaz"; }
İki gerçek senaryo: kendi framework'ünü yaz
Reflection ve annotations'ın asıl gücünü, onları birleştiren iki minyatür framework'le göreceğiz.
1) Mini IoC container (Spring'in kalbi)
Spring'in en temel işi, nesnelerini senin yerine oluşturup bağımlılıklarını enjekte
etmektir. Örnek 2 (./Ornek2.java) bunun küçük bir versiyonunu kurar: arayüz → uygulama
eşlemelerini kaydedersin, coz(SiparisServisi.class) dediğinde container constructor'a bakar,
@Inject işaretli constructor'ın bağımlılıklarını (Depo, MesajGonderici) özyinelemeli
çözer ve nesneyi otomatik kurar:
container.kaydet(Depo.class, BellekDepo.class); SiparisServisi s = container.coz(SiparisServisi.class); // bağımlılıklar otomatik enjekte
Bu, Spring'in @Autowired + constructor injection mekanizmasının özüdür. Gerçeği çok daha
fazlasını yapar (kapsam yönetimi, döngü tespiti, proxy'ler) ama fikir aynıdır.
2) Mini doğrulama framework'ü (Bean Validation'ın kalbi)
Spring/Hibernate'te bir DTO'yu @NotBlank, @Min, @Size ile işaretleyip @Valid dersin;
çatı senin yerine kuralları kontrol eder. Örnek 3 (./Ornek3.java) bunun minyatürünü yazar:
@NotBlank, @Email, @Min, @Length anotasyonlarını tanımlar; bir Dogrulayici, nesnenin
alanlarını reflection ile gezip her alandaki anotasyonlara göre kuralları uygular ve tüm
ihlalleri toplar:
@NotBlank(mesaj = "ad zorunludur") String ad; @Min(deger = 18) int yas; List<String> ihlaller = Dogrulayici.dogrula(form);
Maliyet ve dikkat
Reflection güçlüdür ama bedeli vardır: derleyici tip denetimini atlar (hatalar çalışma zamanına kayar), normal çağrılardan yavaştır ve kodu kırılganlaştırabilir. Bu yüzden günlük iş mantığında nadiren gerekir; asıl yeri, "her tiple çalışması gereken" genel amaçlı kütüphane ve framework'lerdir. Sen genelde bu framework'leri kullanırsın; ama nasıl çalıştıklarını bilmek, onları doğru ve bilinçli kullanmanı sağlar.
Özet
Reflection ile kodu çalışma zamanında inceleyip ona müdahale etmeyi; annotations ile koda meta-veri eklemeyi öğrendik. Daha da önemlisi, ikisini birleştirerek mini bir IoC container ve mini bir doğrulama framework'ü yazdık — yani Spring ve Hibernate'in altında yatan mekanizmayı kendi elinle kurdun. Bu, ilerideki Spring konularını "sihir" değil, anlaşılır bir mühendislik olarak görmeni sağlayacak. Sırada, programını gerçek bir veritabanına bağlamak: JDBC.
▶ Kod Örnekleri(3)
Ornek1
çalıştırılabilir1// Ornek1: Reflection ile genel amaçlı bir "nesne inceleyici" ve serializer.
2// Gerçek hayatta loglama, JSON üretme ve ORM kütüphaneleri tam olarak bunu yapar:
3// bir nesnenin alanlarını çalışma zamanında okuyup işler.
4// Çalıştırma: java Ornek1.java
5import java.lang.reflect.Field;
6import java.lang.reflect.Modifier;
7import java.util.LinkedHashMap;
8import java.util.Map;
9
10public class Ornek1 {
11
12 public static void main(String[] args) {
13 Urun urun = new Urun(101, "Mekanik Klavye", 1450.0, true);
14
15 // 1) Sınıf hakkında üst düzey bilgi (metadata).
16 Class<?> c = urun.getClass();
17 System.out.println("Sınıf : " + c.getSimpleName());
18 System.out.println("Üst sınıf : " + c.getSuperclass().getSimpleName());
19 System.out.println("Alan sayısı: " + c.getDeclaredFields().length);
20
21 // 2) Herhangi bir nesneyi alanlarıyla Map'e çevir (mini serializer).
22 // JSON/loglama kütüphanelerinin temel fikri budur.
23 System.out.println("\nnesneToMap:");
24 Map<String, Object> harita = nesneToMap(urun);
25 harita.forEach((k, v) -> System.out.println(" " + k + " = " + v));
26
27 // 3) Genel amaçlı bir toString üreticisi (her sınıf için çalışır).
28 System.out.println("\nGenel toString:");
29 System.out.println(" " + genelToString(urun));
30 System.out.println(" " + genelToString(new Musteri("Ada", "ada@site.com")));
31 }
32
33 // Reflection ile tüm alanları okuyup Map'e koyar — tip ne olursa olsun çalışır.
34 static Map<String, Object> nesneToMap(Object nesne) {
35 Map<String, Object> sonuc = new LinkedHashMap<>();
36 for (Field f : nesne.getClass().getDeclaredFields()) {
37 if (Modifier.isStatic(f.getModifiers())) continue; // static alanları atla
38 f.setAccessible(true); // private alanlara eriş
39 try {
40 sonuc.put(f.getName(), f.get(nesne));
41 } catch (IllegalAccessException e) {
42 sonuc.put(f.getName(), "<erişilemedi>");
43 }
44 }
45 return sonuc;
46 }
47
48 static String genelToString(Object nesne) {
49 StringBuilder sb = new StringBuilder(nesne.getClass().getSimpleName()).append("{");
50 Field[] alanlar = nesne.getClass().getDeclaredFields();
51 for (int i = 0; i < alanlar.length; i++) {
52 alanlar[i].setAccessible(true);
53 try {
54 sb.append(alanlar[i].getName()).append("=").append(alanlar[i].get(nesne));
55 } catch (IllegalAccessException ignored) {}
56 if (i < alanlar.length - 1) sb.append(", ");
57 }
58 return sb.append("}").toString();
59 }
60}
61
62class Urun {
63 private int id;
64 private String ad;
65 private double fiyat;
66 private boolean stokta;
67 Urun(int id, String ad, double fiyat, boolean stokta) {
68 this.id = id; this.ad = ad; this.fiyat = fiyat; this.stokta = stokta;
69 }
70}
71
72class Musteri {
73 private String ad;
74 private String eposta;
75 Musteri(String ad, String eposta) { this.ad = ad; this.eposta = eposta; }
76}Ornek2
çalıştırılabilir1// Ornek2: Reflection ile MİNİ bir IoC (Inversion of Control) container.
2// Spring'in @Autowired/constructor injection sihrinin küçük bir benzeri:
3// container, bir sınıfın constructor bağımlılıklarını otomatik bulup oluşturur.
4// Çalıştırma: java Ornek2.java
5import java.lang.annotation.ElementType;
6import java.lang.annotation.Retention;
7import java.lang.annotation.RetentionPolicy;
8import java.lang.annotation.Target;
9import java.lang.reflect.Constructor;
10import java.util.HashMap;
11import java.util.Map;
12
13public class Ornek2 {
14
15 public static void main(String[] args) {
16 MiniContainer container = new MiniContainer();
17 // Arayüz -> uygulama eşlemelerini kaydet.
18 container.kaydet(MesajGonderici.class, EpostaGonderici.class);
19 container.kaydet(Depo.class, BellekDepo.class);
20 container.kaydet(SiparisServisi.class, SiparisServisi.class);
21
22 // Container, SiparisServisi'nin constructor'ındaki bağımlılıkları
23 // (Depo, MesajGonderici) reflection ile çözüp otomatik enjekte eder.
24 SiparisServisi servis = container.coz(SiparisServisi.class);
25 servis.siparisOlustur("Klavye");
26 servis.siparisOlustur("Mouse");
27
28 System.out.println("\nNot: Spring'in IoC container'ı tam olarak bunu yapar (çok daha fazlasıyla).");
29 }
30}
31
32// @Inject: hangi constructor'ın enjeksiyon için kullanılacağını işaretler.
33@Retention(RetentionPolicy.RUNTIME)
34@Target(ElementType.CONSTRUCTOR)
35@interface Inject {}
36
37class MiniContainer {
38 private final Map<Class<?>, Class<?>> eslemeler = new HashMap<>();
39
40 void kaydet(Class<?> arayuz, Class<?> uygulama) {
41 eslemeler.put(arayuz, uygulama);
42 }
43
44 // Bir tipi çözer: uygulamasını bulur, constructor bağımlılıklarını
45 // özyinelemeli (recursive) çözer ve nesneyi oluşturur.
46 @SuppressWarnings("unchecked")
47 <T> T coz(Class<T> tip) {
48 Class<?> uygulama = eslemeler.getOrDefault(tip, tip);
49 try {
50 Constructor<?> ctor = enjeksiyonCtor(uygulama);
51 Class<?>[] paramTipleri = ctor.getParameterTypes();
52 Object[] paramlar = new Object[paramTipleri.length];
53 for (int i = 0; i < paramTipleri.length; i++) {
54 paramlar[i] = coz(paramTipleri[i]); // her bağımlılığı özyinelemeli çöz
55 }
56 System.out.println("[container] oluşturuluyor: " + uygulama.getSimpleName());
57 return (T) ctor.newInstance(paramlar);
58 } catch (Exception e) {
59 throw new RuntimeException("Çözülemedi: " + tip.getSimpleName(), e);
60 }
61 }
62
63 // @Inject işaretli constructor varsa onu, yoksa ilk constructor'ı kullan.
64 private Constructor<?> enjeksiyonCtor(Class<?> uygulama) {
65 for (Constructor<?> ctor : uygulama.getDeclaredConstructors()) {
66 if (ctor.isAnnotationPresent(Inject.class)) return ctor;
67 }
68 return uygulama.getDeclaredConstructors()[0];
69 }
70}
71
72interface MesajGonderici { void gonder(String m); }
73interface Depo { void kaydet(String k); }
74
75class EpostaGonderici implements MesajGonderici {
76 public void gonder(String m) { System.out.println(" [e-posta] " + m); }
77}
78class BellekDepo implements Depo {
79 public void kaydet(String k) { System.out.println(" [depo] kaydedildi: " + k); }
80}
81
82class SiparisServisi {
83 private final Depo depo;
84 private final MesajGonderici gonderici;
85
86 // Container bu constructor'ı bulup Depo ve MesajGonderici'yi enjekte eder.
87 @Inject
88 SiparisServisi(Depo depo, MesajGonderici gonderici) {
89 this.depo = depo;
90 this.gonderici = gonderici;
91 }
92
93 void siparisOlustur(String urun) {
94 depo.kaydet(urun);
95 gonderici.gonder("Siparişiniz alındı: " + urun);
96 }
97}Ornek3
çalıştırılabilir1// Ornek3: Reflection + özel annotation'larla MİNİ bir doğrulama (validation) framework'ü.
2// Spring/Hibernate'in @NotBlank, @Min, @Size gibi anotasyonlarını @Valid ile nasıl
3// işlediğinin küçük bir benzeri.
4// Çalıştırma: java Ornek3.java
5import java.lang.annotation.ElementType;
6import java.lang.annotation.Retention;
7import java.lang.annotation.RetentionPolicy;
8import java.lang.annotation.Target;
9import java.lang.reflect.Field;
10import java.util.ArrayList;
11import java.util.List;
12
13public class Ornek3 {
14
15 public static void main(String[] args) throws IllegalAccessException {
16 KullaniciFormu gecerli = new KullaniciFormu("Ada", "ada@site.com", 30, "guclu-parola");
17 KullaniciFormu hatali = new KullaniciFormu("", "gecersiz-eposta", 15, "123");
18
19 System.out.println("Geçerli form -> ihlaller: " + Dogrulayici.dogrula(gecerli));
20 System.out.println("\nHatalı form -> ihlaller:");
21 for (String ihlal : Dogrulayici.dogrula(hatali)) {
22 System.out.println(" - " + ihlal);
23 }
24 }
25}
26
27// --- Annotation'lar (kurallar) ---
28@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
29@interface NotBlank { String mesaj() default "boş olamaz"; }
30
31@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
32@interface Min { int deger(); }
33
34@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
35@interface Length { int min() default 0; int max() default Integer.MAX_VALUE; }
36
37@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
38@interface Email {}
39
40// --- Doğrulanacak veri sınıfı (anotasyonlarla işaretli) ---
41class KullaniciFormu {
42 @NotBlank(mesaj = "ad zorunludur")
43 String ad;
44
45 @NotBlank @Email
46 String eposta;
47
48 @Min(deger = 18)
49 int yas;
50
51 @Length(min = 8, max = 32)
52 String parola;
53
54 KullaniciFormu(String ad, String eposta, int yas, String parola) {
55 this.ad = ad; this.eposta = eposta; this.yas = yas; this.parola = parola;
56 }
57}
58
59// --- Doğrulayıcı: anotasyonları reflection ile okuyup kuralları uygular ---
60class Dogrulayici {
61 static List<String> dogrula(Object nesne) throws IllegalAccessException {
62 List<String> ihlaller = new ArrayList<>();
63 for (Field f : nesne.getClass().getDeclaredFields()) {
64 f.setAccessible(true);
65 Object deger = f.get(nesne);
66
67 if (f.isAnnotationPresent(NotBlank.class)) {
68 if (deger == null || deger.toString().isBlank()) {
69 ihlaller.add(f.getName() + ": " + f.getAnnotation(NotBlank.class).mesaj());
70 }
71 }
72 if (f.isAnnotationPresent(Email.class) && deger != null) {
73 if (!deger.toString().matches("[^@\\s]+@[^@\\s]+\\.[^@\\s]+")) {
74 ihlaller.add(f.getName() + ": geçerli bir e-posta değil");
75 }
76 }
77 if (f.isAnnotationPresent(Min.class) && deger instanceof Integer sayi) {
78 int min = f.getAnnotation(Min.class).deger();
79 if (sayi < min) ihlaller.add(f.getName() + ": en az " + min + " olmalı (mevcut " + sayi + ")");
80 }
81 if (f.isAnnotationPresent(Length.class) && deger != null) {
82 Length l = f.getAnnotation(Length.class);
83 int uz = deger.toString().length();
84 if (uz < l.min() || uz > l.max()) {
85 ihlaller.add(f.getName() + ": uzunluk " + l.min() + ".." + l.max() + " arasında olmalı (mevcut " + uz + ")");
86 }
87 }
88 }
89 return ihlaller;
90 }
91}