Eğitim Portalı/Java/Reflection ve Annotations
Java01-java/16-reflection-ve-annotations

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ılabilir
Ornek1.java
1// 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}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.

Ornek2

çalıştırılabilir
Ornek2.java
1// 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}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.

Ornek3

çalıştırılabilir
Ornek3.java
1// 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}
Çıktı yerel JDK 21 ile yakalandı — tarayıcıda JVM çalışmaz.