İçeriğe geç
academia.sh

Ders 04 / 19

Yapılandırma Kaynakları

Bir ayar değeri üç kaynaktan birinden gelir ve öncelik sırası nesne kabının kuralıdır, kaynakta hiçbir yerde yazmaz: dışarıdan verilen değer bir dosya kaynağını, dosya kaynağı da gömülü varsayılanı bastırır. Yanlış yazılmış bir ayar adı kabı hiç rahatsız etmez — program sessizce gömülü varsayılana düşer ve dolu yerine bos okur.

İçindekiler

Önceki ders hazirla()’nın baslangic alanını okuyarak durum değerini doldurduğunu gösterdi, ama o alanın kendisinin nereden geldiğini hiç sormadı — yalnız kurulum sırasında dolu olduğunu varsaydı. @Kap.Ayar(ad = "depo.baslangic", varsayilan = "bos") satırı iki şey söylüyor: bu alanın bir dış değeri olabilir, ve o değer verilmezse "bos" kullanılsın. Ama “dışarıdan verilen değer” tek bir yerden gelmiyor. Bu ders o kaynağı üçe böler ve aralarındaki öncelik sırasını ölçer.

Üçüncü ders bir kap bileşeninin kurulumunu üç adıma bölmüştü: yapıcı, ayar alanlarının doldurulması, kurulum yöntemi — ve bu üç adımın sırası kabın kod satırlarının sırasıydı, kaynakta yazan bir sayı değil. Bu ders aynı örüntüyü bir düzey daha derine indiriyor: ayar alanlarının doldurulması kendi içinde tek bir adım değil, kendi sırası olan üç ayrı kaynağın sorgulanması. Önceki dersin “hangi adım önce” sorusu burada “hangi kaynak önce” sorusuna dönüşüyor, ve yanıt yine aynı yerde: kabın kendi kodunda, kaynakta değil.

Üç Kaynak, Tek Değer

Bir ayar değeri şu üç yerden birinden gelebilir: dışarıdan verilen bir değer (programı çalıştıran tarafın doğrudan sağladığı, örneğin bir komut satırı bağımsız değişkeni ya da çalışma zamanı bayrağı), bir dosya kaynağı (kap kurulurken okunan, birden çok ayarı birden taşıyan bir yapılandırma kümesi) ve gömülü varsayılan (açıklamanın kendi varsayilan() değeri, sınıfın yazarının koyduğu son çare). Bu üçü aynı anda var olabilir, ve kap her ayar için tek bir kazananı seçmek zorunda.

Kazanan seçimi bir zincir: önce dış kaynağa bakılıyor, orada yoksa dosyaya, orada da yoksa gömülü varsayılana düşülüyor. Bu zincirin sırası kabın kuralı; hiçbir ayar bildirimi “önce dış kaynağa bak” demiyor, bu davranış doldur yönteminin kod satırlarının sırasından geliyor: if (disKaynak.containsKey(...)) ... else if (dosya.containsKey(...)) ... else .... Bu sıra tersine çevrilseydi (önce dosyaya, sonra dış kaynağa bakılsaydı) aynı üç kaynak farklı bir kazanan üretirdi — kaynaktaki hiçbir ayar tanımı bu sırayı değiştiremez, çünkü sıra ayar tanımlarının değil, kabın kendi kodunun içinde.

Bu sıralamanın kendine göre bir mantığı var, ama o mantık da kaynakta yazmıyor: dış kaynak en yüksek önceliği alıyor çünkü programı çalıştıran taraf her seferinde farklı bir değer vermek isteyebilir — aynı sınıf değişmeden, iki ayrı koşumda iki ayrı değerle çalışabilsin diye. Dosya kaynağı ondan bir alt basamakta, çünkü bir kurulumun tamamına ait, tek bir koşuma özgü değil. Gömülü varsayılan en alt basamakta, çünkü sınıfı yazan kişinin “hiçbir şey verilmezse bu olsun” dediği son çare. Üç basamak da makul bir gerekçeye dayanıyor, ama üçünü sıralayan gerekçe bir yorum satırı olarak bile kaynakta durmuyor — yalnız doldur yönteminin if-else sırasında gömülü.

containsKey denetiminin kendisi de bir ayrıntı taşıyor: bir kaynak, bir anahtara boş bir dizgi değer vermiş olsa bile o kaynak kazanmış sayılıyor, çünkü denetim null kontrolü değil anahtarın varlığını soruyor. Bu ölçümde böyle bir durum yok — dört ayarın hiçbiri boş dizgiyle tanımlı değil — ama düzeneğin kendisi bu ayrımı yapıyor: “değer boş” ile “değer hiç verilmemiş” iki ayrı durum, ve doldur yalnız ikincisinde bir sonraki kaynağa geçiyor.

  • CK16. Öncelik zinciri sabit üç adım: dış kaynak, dosya, gömülü varsayılan. Bu ölçüm dördüncü bir kaynak (örneğin ortam değişkeni) eklemiyor.
  • CK17. Dış kaynak ve dosya, gerçek bir dosya sisteminden ya da komut satırından okunmuyor; ikisi de bellekte tutulan birer Map<String, String>.
  • CK18. Bir ayarın hangi kaynaktan geldiği yalnız containsKey denetimiyle belirleniyor; anahtarın değeri boş dizgi olsa bile o kaynak “kazanmış” sayılıyor.
  • CK19. Dört ayar dört farklı senaryoyu temsil ediyor: yalnız dış kaynakta olan, hem dış kaynakta hem dosyada olan, yalnız dosyada olan, hiçbirinde olmayan.
  • CK20. Kazanan kaynak sayımı bir harita üzerinde tutuluyor; sayaç sıfırdan başlıyor ve yalnız bu ölçümün kapsadığı dört ayarı sayıyor.
  • CK21. Süre, dosya yolu ya da gerçek bir komut satırı bağımsız değişkeni bu ölçümde yok; taşınan tek şey ayar adı ve dizgi değeri.
// Yapilandirma.java — uc yapilandirma kaynagi arasindaki oncelik sirasi: dis kaynak, dosya kaynagi, gomulu varsayilan
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Yapilandirma {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    @interface Ayar { String ad(); String varsayilan(); }

    static final Map<String, Integer> kazananSayisi = new LinkedHashMap<>();
    static void kazandi(String kaynak) { kazananSayisi.merge(kaynak, 1, Integer::sum); }

    final Map<String, String> disKaynak;
    final Map<String, String> dosya;

    Yapilandirma(Map<String, String> disKaynak, Map<String, String> dosya) {
        this.disKaynak = disKaynak;
        this.dosya = dosya;
    }

    void doldur(Object ornek) throws Exception {
        for (Field f : ornek.getClass().getDeclaredFields()) {
            Ayar a = f.getAnnotation(Ayar.class);
            if (a == null) continue;
            f.setAccessible(true);
            String deger; String kaynak;
            if (disKaynak.containsKey(a.ad())) { deger = disKaynak.get(a.ad()); kaynak = "dis-kaynak"; }
            else if (dosya.containsKey(a.ad())) { deger = dosya.get(a.ad()); kaynak = "dosya"; }
            else { deger = a.varsayilan(); kaynak = "gomulu-varsayilan"; }
            f.set(ornek, deger);
            kazandi(kaynak);
            System.out.printf("%-24s %-6s kazanan: %s%n", a.ad(), deger, kaynak);
        }
    }

    public static void main(String[] args) throws Exception {
        Map<String, String> dis = new LinkedHashMap<>();
        dis.put("depo.baglanti-havuzu", "50");

        Map<String, String> dosya = new LinkedHashMap<>();
        dosya.put("depo.baglanti-havuzu", "10");
        dosya.put("depo.zaman-asimi", "30");
        dosya.put("depo.baslangic.durumu", "dolu");

        Yapilandirma y = new Yapilandirma(dis, dosya);
        AyarliBilesen b = new AyarliBilesen();

        System.out.println("-- her ayarin hangi kaynaktan geldigi --");
        y.doldur(b);

        System.out.println();
        System.out.println("-- kazanan kaynak sayimi --");
        kazananSayisi.forEach((k, v) -> System.out.printf("%-20s %d%n", k, v));

        System.out.println();
        System.out.println("okunan degerler:");
        System.out.println("havuz boyutu     : " + b.havuzBoyutu);
        System.out.println("zaman asimi      : " + b.zamanAsimi);
        System.out.println("baslangic durumu : " + b.baslangicDurumu);
        System.out.println("onbellek boyutu  : " + b.onbellekBoyutu);
    }
}

class AyarliBilesen {
    @Yapilandirma.Ayar(ad = "depo.baglanti-havuzu", varsayilan = "5") String havuzBoyutu;
    @Yapilandirma.Ayar(ad = "depo.zaman-asimi", varsayilan = "10") String zamanAsimi;
    @Yapilandirma.Ayar(ad = "depo.baslangic.durumu", varsayilan = "bos") String baslangicDurumu;
    @Yapilandirma.Ayar(ad = "depo.onbellek.boyutu", varsayilan = "100") String onbellekBoyutu;
}
-- her ayarin hangi kaynaktan geldigi --
depo.baglanti-havuzu     50     kazanan: dis-kaynak
depo.zaman-asimi         30     kazanan: dosya
depo.baslangic.durumu    dolu   kazanan: dosya
depo.onbellek.boyutu     100    kazanan: gomulu-varsayilan

-- kazanan kaynak sayimi --
dis-kaynak           1
dosya                2
gomulu-varsayilan    1

okunan degerler:
havuz boyutu     : 50
zaman asimi      : 30
baslangic durumu : dolu
onbellek boyutu  : 100

Dört Ayar, Dört Farklı Kazanan

depo.baglanti-havuzu hem dış kaynakta (50) hem dosyada (10) tanımlı, ve kazanan dış kaynak — zincirin ilk halkası. Bu, bir ayarın kaç yerde tanımlı olduğunun önemli olmadığını gösteriyor: kazananı belirleyen tek şey, hangi kaynağın zincirde daha önce sorgulandığı. depo.zaman-asimi ve depo.baslangic.durumu yalnız dosyada var, dış kaynakta yok; ikisi de dosyayı kazanıyor çünkü ilk denetim (disKaynak.containsKey) false dönüp ikinci denetime geçiyor. depo.onbellek.boyutu hiçbir kaynakta yok — zincirin üçüncü halkasına, gömülü varsayılana düşüyor.

kazananSayisi bir LinkedHashMap üzerinde tutuluyor, HashMap değil — bu ders için gereken tek şey doğru sayım olsa da, LinkedHashMap ekleme sırasını koruduğu için çıktı da her koşumda aynı sırada basılıyor. Bu küçük seçim bile bir hatırlatma: kabın kendi iç veri yapısı seçimleri, ölçülen davranışı etkilemese bile çıktının okunabilirliğini etkiliyor, ve bu seçim de kaynaktaki ayar bildirimlerinin hiçbirinde görünmüyor.

Sayım satırı bunu özetliyor: dört ayarın biri dış kaynaktan, ikisi dosyadan, biri gömülü varsayılandan geldi. Bu sayı öncelik sırasının etkisinin ölçüsü — kaç ayarın hangi kaynağın “kazanmasına” bağlı olduğunu gösteriyor. Kod Olarak Altyapı kursu, dışarıdan verilen bir değerin kayıt altına alınıp alınmadığını ölçmüştü; bu ders aynı “dışarıdan verilen değer” fikrini alıyor ama farklı bir soru soruyor — kayıt değil, hangi bileşenin o değeri gerçekten gördüğü.

f.setAccessible(true) çağrısı burada da geçiyor, çünkü AyarliBilesen’in alanları paket düzeyinde görünür ve doldur yöntemi bu alanlara genel bir Object üzerinden erişiyor — ikinci dersin Servis yapıcısında görülen aynı erişim denetimi atlaması, bu kez alanlar için. Aradaki fark: burada yalnız değer yazılıyor, bir yöntem çağrılmıyor.

Ayar adının kendisi de bir tip değil, yalnız bir dizgi — String ad(). Bu, üçüncü dersin kapsam adı gözlemiyle aynı sınıftan bir seçim: derleyici bir ayar adının gerçekten var olan bir kaynak anahtarına karşılık gelip gelmediğini denetleyemiyor, çünkü elindeki tek şey karakter dizisi. "depo.baglanti-havuzu" ile "depo.baglanti_havuzu" (tire yerine alt çizgi) derleyici için aynı ölçüde geçerli iki dizgi, ama containsKey için tamamen ayrı iki anahtar. Bu ders dört ayarın hepsinde doğru yazılmış adları kullanıyor; bir sonraki bölüm bu varsayımı kasıtlı olarak bozuyor.

Sıra Değişince Kazanan Değişiyor

Önceki bölümün iddiası — sıranın kabın kendi kodunda gömülü olduğu — düz metinde kalırsa kanıtlanmamış bir savdan ibaret. Aynı iki kaynağı, aynı iki değeri, yalnız sorgulama sırasını tersine çevirerek koşturmak bu savı doğruluyor:

// TersSira.java — ayni iki kaynak, sadece sorgulama sirasi degisti, kazanan da degisiyor
import java.util.*;

public class TersSira {
    static String dosyaOncelikli(Map<String, String> disKaynak, Map<String, String> dosya, String ad, String varsayilan) {
        if (dosya.containsKey(ad)) return dosya.get(ad);
        if (disKaynak.containsKey(ad)) return disKaynak.get(ad);
        return varsayilan;
    }

    public static void main(String[] args) {
        Map<String, String> dis = Map.of("depo.baglanti-havuzu", "50");
        Map<String, String> dosya = Map.of("depo.baglanti-havuzu", "10");

        System.out.println("dis-kaynak once sorguladiginda kazanan  : " + dis.get("depo.baglanti-havuzu"));
        System.out.println("dosya once sorguladiginda kazanan       : "
                + dosyaOncelikli(dis, dosya, "depo.baglanti-havuzu", "5"));
    }
}
dis-kaynak once sorguladiginda kazanan  : 50
dosya once sorguladiginda kazanan       : 10

dis ve dosya haritalarının içeriği bu iki satır arasında hiç değişmedi — depo.baglanti -havuzu anahtarı ikisinde de aynı değerlerle duruyor. Değişen tek şey, dosyaOncelikli yönteminin hangi haritaya önce baktığı. Yapilandirma.doldur dış kaynağı önce sorguluyor ve 50 kazanıyor; burada yazılan dosyaOncelikli dosyayı önce sorguluyor ve 10 kazanıyor. Kaynaklardaki veri aynı, ayar adı aynı, bileşenin kendisi hiç yok bu ölçümde — yalnız bir yöntemin içindeki iki if satırının sırası tersine döndü ve kazanan değişti. Bu, öncelik sırasının kaynaktaki hiçbir ayar bildirimine değil, yalnız kabı yazan kodun kendi sırasına bağlı olduğunun doğrudan kanıtı.

Sınırlayıcı Ölçüm: Yazım Farkı

depo.baslangic.durumu anahtarı dosyada "dolu" değeriyle duruyor. Bir bileşen bu değeri okumak istiyor, ama açıklamasına anahtarı yanlış yazıyor — Türkçe ı yerine i (ya da tam tersi) kullanıyor:

// YanlisAyarAdi.java — dosyada duran deger, ayar adindaki yazim farki yuzunden hic kazanamiyor
public class YanlisAyarAdi {
    public static void main(String[] args) throws Exception {
        java.util.Map<String, String> dis = java.util.Map.of();
        java.util.Map<String, String> dosya = java.util.Map.of("depo.baslangic.durumu", "dolu");

        Yapilandirma y = new Yapilandirma(dis, dosya);
        YazimFarkli b = new YazimFarkli();
        y.doldur(b);

        System.out.println("dosyada duran deger      : dolu");
        System.out.println("bilesenin okudugu deger  : " + b.durum);
    }
}

class YazimFarkli {
    @Yapilandirma.Ayar(ad = "depo.baslangıc.durumu", varsayilan = "bos") String durum;
}
depo.baslangıc.durumu    bos    kazanan: gomulu-varsayilan
dosyada duran deger      : dolu
bilesenin okudugu deger  : bos

YazimFarkli sınıfının ad değeri "depo.baslangıc.durumu" (noktalı ı), dosyadaki anahtar ise "depo.baslangic.durumu" (noktasız i) — iki dizgi Java’nın equals karşılaştırmasında birbirinden tamamen farklı, containsKey her ikisi için de false dönüyor, ve doldur yöntemi zincirin üçüncü halkasına düşüyor. Hiçbir istisna yok, hiçbir uyarı yok, hiçbir günlük satırı “anahtar bulunamadı” demiyor — program sessizce "bos" okuyor, dosyada duran "dolu" değeri hiç görülmeden kalıyor.

Bu, ikinci dersteki bağlanmamış arayüz hatasının tam tersi: orada kap hemen ve gürültülü bir istisna fırlatıyordu, burada kap hiç ses çıkarmıyor. Öncelik sırası kaynakta hiçbir yerde yazmadığı gibi, bir kaynağın “kaybetmesi” de kaynakta hiçbir iz bırakmıyor — kaybeden kaynak sanki hiç var olmamış gibi davranılıyor. Bu kusuru fark etmenin tek yolu, dosyada duran değer ile bileşenin okuduğu değeri elle karşılaştırmak; kodun kendisi hiçbir zaman bu farkı işaret etmiyor.

Bu davranışın neden değişmediğine bakmak faydalı: doldur yöntemi bir ayar adının dosyada “neredeyse eşleştiğini” fark edebilecek bir mekanizma taşımıyor, çünkü öyle bir mekanizma kendi başına yeni bir kusur kaynağı olurdu — hangi yazım farkının “kabul edilebilir” olduğuna kim karar verecek? Kap bu soruyu hiç sormuyor, yalnız tam eşleşmeyi arıyor. Bu, üçüncü dersteki tanınmayan kapsam adıyla aynı tasarım tercihi: doğrulamayı erken ve otomatik yapmak yerine, doğrulamayı tamamen dışarıda bırakıp sonucu davranışla göstermek. İkisinde de kusur bir istisna değil, beklenenden farklı ama geçerli görünen bir çıktı — YanlisKapsam’da iki ayrı nesne, burada "dolu" yerine "bos".

Bu iki gözlem — sıra değişince kazananın değişmesi, yazım farkı yüzünden hiçbir kaynağın kazanamaması — aynı gerçeğin iki yüzü. Birincisinde üç kaynak da doğru adla tanımlıydı, ve sıra değişikliği hangisinin göründüğünü belirledi. İkincisinde sıra hiç değişmedi, ama ayar adı yanlış yazıldığından zincirin ilk iki halkası hiç sorgulanamadı bile — containsKey sorusu yanlış anahtara soruldu. İki kusur da aynı kökten geliyor: bir ayarın hangi değeri taşıyacağı, kaynaktaki hiçbir satırdan doğrudan okunamayan, yalnız kabın kuralını ve kaynakların o an ne içerdiğini bilerek çıkarılabilen bir sonuç.

Özet

  • Bir ayar değeri üç kaynaktan gelebilir: dışarıdan verilen bir değer, bir dosya kaynağı, ve açıklamanın gömülü varsayılanı; kazananı belirleyen zincir kabın kendi kod sırası.
  • Bir ayarın birden çok kaynakta tanımlı olması önemli değil — yalnız zincirde önce sorgulanan kaynak kazanıyor.
  • Dört ayardan biri dış kaynaktan, ikisi dosyadan, biri gömülü varsayılandan geldi; bu dağılım öncelik sırasının etkisinin doğrudan ölçüsü.
  • Öncelik sırasının kendisi kaynakta hiçbir yerde yazmaz; hangi kaynağın kazandığını bilmek için kabın kodunu bilmek gerekir.
  • Yanlış yazılmış bir ayar adı hiçbir hata vermez — kap sessizce gömülü varsayılana düşer ve dosyada duran gerçek değer hiç görülmeden kalır.

Sonraki Adım

Dördüncü ayar (depo.onbellek.boyutu) hiçbir kaynakta tanımlı değildi ve gömülü varsayılana düştü — ama bu, kabın “hiçbir şey yapmadığı” anlamına gelmiyordu, yalnız zincirin son halkasına ulaştığı anlamına geliyordu. Bazı durumlarda kabın kendisi, bir bileşenin hiç bildirilmediğini fark edip kendi varsayılanını devreye sokuyor — bir ayar değeri değil, bir bileşenin kendisi. Sıradaki ders bu farklı türden varsayılanı ölçer: bir bileşen hiç yazılmadığında kap ne yapıyor, ve bu karar neden kaynakta “koşullu” bir sözcükle hiç görünmüyor.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat