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
containsKeydenetimiyle 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.