İçeriğe geç
academia.sh

Ders 12 / 19

İşlem Yönetimi

İki çağrı yolu yan yana koşturuluyor ve bildirimsel işlem sınırının kaç kez açıldığı sayılıyor. Bir bileşenin kendi kendini çağırması vekili atlıyor: iç çağrı için sınır hiç açılmıyor, hiçbir hata çıkmıyor, ve bu sessizlik kaynakta hiçbir yerde işaretli değil.

İçindekiler

Önceki ders bir nesnenin alanlarının ne zaman yüklendiğini ölçtü, ama bu yüklemenin ve üzerindeki değişikliklerin nerede başlayıp nerede bittiği hiç sorulmadı. Bu ders bunu ele alıyor: bildirimsel bir işlem sınırının kaynakta bir açıklama olduğunu, ve bir bileşenin kendi kendini çağırmasının bu sınırı nasıl atladığını ölçüyor. İşlemin kendisi — bir dizi değişikliğin ya tamamen ya hiç uygulanmaması, ve yalıtım düzeyleri — M17/K04 Veritabanı Yönetimi kursunda kavram olarak kuruldu ve burada tekrarlanmıyor; bu dersin sorusu ondan ayrı: sınırın nerede açılıp kapandığı, ve bu sınırı neyin çizdiği.

Gerçek bir veritabanı bağlantısı burada da yok. Bir işlem sınırı, sinirAcilan adlı bir sayacın artması olarak modelleniyor — gerçek bir işlemde bu, bir bağlantının işlem kipine alınması ve sonunda onaylanması ya da geri alınmasıdır; burada yalnız “sınır kaç kez açıldı” sorusu ölçülüyor, açılan sınırın içinde ne olduğu değil. Bildirimsel sınır, kaynakta hiçbir baslatIslemi() çağrısı olmadan, yalnız bir yöntemin üzerindeki @Islem açıklamasından doğuyor.

“Bildirimsel” sözcüğü tam olarak bunu anlatıyor: sınırı çizen taraf, yöntemi çağıran kod değil, yöntemin üzerinde duran açıklama. Önceki bir ders bu bildirim biçimini doğrulama kurallarında görmüştü — bir alanın kısıtlaması da açıklamada duruyordu, onu okuyan taraf Kap’tı. İşlem sınırında fark şu: doğrulamanın uygulanıp uygulanmaması yalnız hangi yöntemin çağrıldığına bağlıydı, işlem sınırının açılıp açılmaması ise çağrının nereden geldiğine de bağlı. Bu fark, dersin geri kalanının asıl konusu.

İki Çağrı Yolu, İki Ayrı Sınır Sayısı

  • WV25. @Islem, hem yatir hem yatirVeBildir yönteminin üzerinde duruyor. İkisi de aynı gerçekleştirim sınıfının üyesi.
  • WV26. Vekil, çağrılan yöntemi hedef nesnenin kendi sınıfında yeniden arayıp @Islem açıklamasını orada kontrol ediyor; bu, m.invoke çağrısından önce yapılıyor, böylece sınır açma kararı gerçek çağrıdan önce veriliyor.

HesapGerceklestirimi.yatirVeBildir kendi içinde yatir(miktar)’ı çağırıyor — bu, aynı nesnenin bir yöntemi kendi başka bir yöntemini çağırması, yani bir iç çağrı. Kap.sarmala bu iki yöntemi de bir vekil arkasına koyuyor, ve vekil yalnız dışarıdan gelen çağrıları görüyor.

Ölçüm iki ayrı kurulumla yapılıyor, ve ikisi de aynı gerçekleştirim sınıfını kullanıyor. Kurulumlar arasındaki tek fark, dışarıdaki çağıranın hangi yöntemleri, kaç kez çağırdığı; HesapGerceklestirimi’nin kendi kaynak kodu iki kurulumda da birebir aynı kalıyor. Bu, önceki derslerdeki “iki kurulum” ölçümlerinden bir bakımdan farklı: orada değişen şey genellikle tarama sırası ya da gerçekleştirim seçimiydi, burada değişen şey yalnız dışarıdan gelen çağrı deseni.

// Kap.java — bildirimsel islem siniri aciklamadan doguyor, kendi kendini cagirma vekili atliyor
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Kap {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD)
    @interface Islem {}

    static int sinirAcilan = 0;
    static List<String> gunluk = new ArrayList<>();

    @SuppressWarnings("unchecked")
    static <T> T sarmala(T hedef, Class<T> arayuz) {
        return (T) Proxy.newProxyInstance(
                Kap.class.getClassLoader(),
                new Class<?>[]{arayuz},
                (v, m, args) -> {
                    Method gercek = hedef.getClass().getMethod(m.getName(), m.getParameterTypes());
                    if (gercek.isAnnotationPresent(Islem.class)) {
                        sinirAcilan++;
                        gunluk.add("sinir-acildi: " + m.getName());
                    }
                    try {
                        return m.invoke(hedef, args);
                    } catch (InvocationTargetException e) {
                        throw e.getCause();
                    }
                });
    }

    public static void main(String[] args) throws Exception {
        HesapGerceklestirimi gercek = new HesapGerceklestirimi();
        Hesap vekil = sarmala(gercek, Hesap.class);

        System.out.println("-- yol A: yalniz yatirVeBildir digaridan cagriliyor --");
        System.out.println(vekil.yatirVeBildir(100));
        System.out.println("sinir acilma sayisi: " + sinirAcilan);
        System.out.println("gunluk: " + gunluk);

        sinirAcilan = 0;
        gunluk.clear();
        gercek = new HesapGerceklestirimi();
        vekil = sarmala(gercek, Hesap.class);

        System.out.println("-- yol B: yatir ve yatirVeBildir ayri ayri digaridan cagriliyor --");
        System.out.println(vekil.yatir(50));
        System.out.println(vekil.yatirVeBildir(100));
        System.out.println("sinir acilma sayisi: " + sinirAcilan);
        System.out.println("gunluk: " + gunluk);

        System.out.println("-- ic cagrinin kendisi vekili hic gormedi --");
        System.out.println("HesapGerceklestirimi.yatirVeBildir icindeki 'yatir(miktar)' cagrisi "
                + "'this' uzerinden yapiliyor, vekil uzerinden degil");
        System.out.println("dogrulama: yol B'de disaridan iki cagri oldu, ic cagriyla birlikte "
                + "toplamda uc kez yatir/yatirVeBildir calisti, ama sinir yalniz iki kez acildi");
        System.out.println("kusur turu: sessiz (istisna yok, ic cagri icin sinir hic acilmadan calisti)");

        System.out.println("-- elle kurulan nesne: kap hic devrede degil --");
        HesapGerceklestirimi elleKurulan = new HesapGerceklestirimi();
        sinirAcilan = 0;
        System.out.println(elleKurulan.yatirVeBildir(100));
        System.out.println("sinir acilma sayisi (vekil hic kurulmadi): " + sinirAcilan);
    }
}

interface Hesap {
    String yatir(int miktar);
    String yatirVeBildir(int miktar);
}

class HesapGerceklestirimi implements Hesap {
    int bakiye = 0;

    @Kap.Islem
    public String yatir(int miktar) {
        bakiye += miktar;
        return "bakiye=" + bakiye;
    }

    @Kap.Islem
    public String yatirVeBildir(int miktar) {
        String sonuc = yatir(miktar);
        return sonuc + "; bildirildi";
    }
}
-- yol A: yalniz yatirVeBildir digaridan cagriliyor --
bakiye=100; bildirildi
sinir acilma sayisi: 1
gunluk: [sinir-acildi: yatirVeBildir]
-- yol B: yatir ve yatirVeBildir ayri ayri digaridan cagriliyor --
bakiye=50
bakiye=150; bildirildi
sinir acilma sayisi: 2
gunluk: [sinir-acildi: yatir, sinir-acildi: yatirVeBildir]
-- ic cagrinin kendisi vekili hic gormedi --
HesapGerceklestirimi.yatirVeBildir icindeki 'yatir(miktar)' cagrisi 'this' uzerinden yapiliyor, vekil uzerinden degil
dogrulama: yol B'de disaridan iki cagri oldu, ic cagriyla birlikte toplamda uc kez yatir/yatirVeBildir calisti, ama sinir yalniz iki kez acildi
kusur turu: sessiz (istisna yok, ic cagri icin sinir hic acilmadan calisti)
-- elle kurulan nesne: kap hic devrede degil --
bakiye=100; bildirildi
sinir acilma sayisi (vekil hic kurulmadi): 0

Yol A’da tek bir dış çağrı var — vekil.yatirVeBildir(100) — ve sınır bir kez açılıyor. Yol B’de iki dış çağrı var — vekil.yatir(50) ve vekil.yatirVeBildir(100) — ve sınır iki kez açılıyor. İkisi arasındaki fark yalnız dışarıdan kaç çağrı yapıldığı; yatirVeBildir’in kendi içinde yatir’ı çağırması hiçbir kurulumda sınır sayısına eklenmiyor. Bu, kursun yayılım dediği şeyin en yalın hâli: bir iç çağrının kendi sınırını mı açacağı, yoksa dışarıdaki sınırın içinde mi kalacağı sorusu — burada yanıt üçüncü bir olasılık, iç çağrının hiçbir sınıra hiç girmemesi, çünkü vekil onu hiç görmüyor.

İç Çağrı Vekili Hiç Görmüyor

  • WV27. sarmala yalnız Hesap arayüzünü uygulayan bir vekil nesnesi döndürüyor; HesapGerceklestirimi’nin kendisi hiç değişmiyor, this hâlâ gerçek nesneyi gösteriyor.
  • WV28. Vekilin araya girebilmesi için bir çağrının onun ürettiği nesne üzerinden geçmesi gerekiyor. yatirVeBildir içindeki yatir(miktar) çağrısı bu nesneyi hiç kullanmıyor.

Bunun nedeni, vekilin nasıl çalıştığında yatıyor. Proxy.newProxyInstance yeni bir nesne üretiyor — vekil — ve bu nesne dışarıdan gelen her çağrıyı yakalayıp sinirAcilan’ı artırdıktan sonra gerçek nesneye (hedef) yönlendiriyor. Ama HesapGerceklestirimi sınıfının kendi kaynak kodu içinde yatir(miktar) yazıldığında, bu çağrı hedef.yatir(miktar) değil, gerçek nesnenin kendi this’i üzerinden yapılan sıradan bir Java yöntem çağrısı. Nesnenin kendisi hiçbir zaman “ben bir vekilin arkasındayım” bilgisine sahip değil — vekil, nesnenin etrafına konan ayrı bir katman, nesnenin bir parçası değil. Bu yüzden yatir’in üzerindeki @Islem açıklaması, bu iç çağrı için hiçbir zaman okunmuyor: onu okuyacak taraf yalnız vekilin çağrı işleyicisi, ve çağrı işleyicisi yalnız vekil üzerinden geçen çağrıları görüyor.

Bu, dersin sınırlayıcı ölçümü: bir bileşenin kendi kendini çağırması vekili atlar ve açıklama hiç uygulanmaz — çağrı içeriden geldiği için sarmalama devreye girmez, hata çıkmaz, sınır açılmaz. Yol B’nin günlüğü bunu doğruluyor: yatir ve yatirVeBildir toplam üç kez çalıştı (dışarıdan iki, içeriden bir), ama günlükte yalnız iki sinir-acildi girdisi var. Ne bir istisna fırlıyor ne bir uyarı basılıyor — program tamamen normal görünen bir yanıt üretiyor, bakiye=150; bildirildi. Kaynak metni okuyan biri yatir’in üzerinde @Islem gördüğü için onun her çağrıldığında bir sınır açacağını varsayabilir; bu varsayım yatirVeBildir üzerinden gelen çağrılar için yanlış, ve yanlışlığı gösteren tek şey sayaç, hiçbir hata mesajı değil.

Bu davranışın kökü, vekilin kurulma biçiminde. Kap.sarmala yeni bir nesne üretiyor, var olan nesneyi değiştirmiyorHesapGerceklestirimi’nin kendi bayt kodunda hiçbir satır eklenmedi, hiçbir yöntem araya giren bir çağrıyla değiştirilmedi. Vekil, hedef’i saran ayrı bir sınıfın çalışma zamanında üretilen bir örneği, ve bu örnek yalnız Hesap arayüzünü uyguladığı için, arayüz üzerinden gelen çağrılar dışında hiçbir yola dahil değil. Bir gerçekleştirim sınıfının içine yazılmış bir çağrı, o sınıfın kendi tanıdığı tek nesneyi görüyor: kendisi. Vekilin varlığından habersiz olmak, gerçekleştirim sınıfının bir eksikliği değil — vekil zaten sınıfın kendisine hiçbir iz bırakmadan, tamamen dışarıdan ekleniyor.

Elle Kurulan Nesnede Sınır Hiç Açılmıyor

Son ölçüm sarmala hiç çağrılmadan kurulan bir HesapGerceklestirimi nesnesiyle yapılıyor. elleKurulan.yatirVeBildir(100) çağrısı normal şekilde tamamlanıyor, bakiye=100; bildirildi döndürüyor — ama sinirAcilan sıfırda kalıyor. Bu, önceki derslerde defalarca görülen kuralın işlem yönetimindeki karşılığı: @Islem açıklaması HesapGerceklestirimi sınıfının üzerinde durmaya devam ediyor, ama onu okuyacak bir vekil hiç kurulmadıysa hiçbir sınır hiçbir zaman açılmıyor. Yol B’deki iç çağrı ile bu elle kurulan nesne arasındaki fark ince ama önemli: iç çağrıda vekil var, yalnız o çağrı için devreye girmiyor; elle kurulan nesnede vekil hiç yok. İkisi de aynı sonucu — sınırın açılmaması — üretiyor, ama biri nesne kabının kendi kuralından (yalnız dışarıdan gelen çağrılar sarmalanır), öteki kabın hiç devrede olmamasından geliyor.

Bu üç kurulumun birlikte gösterdiği şey şu: bildirimsel bir sınırın var olduğunu bilmek, onun her zaman çalışacağını bilmek anlamına gelmiyor. Sınırın açılması üç koşula birden bağlı — açıklama sınıfın üzerinde olmalı, çağrı vekil üzerinden geçmeli, ve çağrı dışarıdan gelmeli. Üçü de sağlanmadığı sürece, kaynakta @Islem yazması hiçbir şey garanti etmiyor.

Bu üç koşulun hiçbiri kaynak metninde tek bir yerde toplu olarak yazılı değil. @Islem açıklaması HesapGerceklestirimi’nin içinde, vekil kurulumu main’in içinde, ve çağrının dışarıdan mı içeriden mi geldiği yalnız çağrının hangi değişken üzerinden yapıldığına bakılarak anlaşılabiliyor — vekil.yatir(...) mı, this.yatir(...)’e denk gelen çıplak yatir(...) mu. Bu üç bilgiyi aynı anda tutmak, bir kod incelemesinin gözden kaçırabileceği bir şey: yatirVeBildir yönteminin gövdesine bakan biri, yatir çağrısının hangi nesne üzerinden yapıldığını fark etmeden geçebilir, çünkü Java söz dizimi yatir(...) ile this.yatir(...)’i ayırt etmiyor — ikisi aynı çağrı, aynı görünüm.

Özet

  • Yalnız yatirVeBildir’in dışarıdan çağrıldığı yolda sınır bir kez açıldı; yatir ile yatirVeBildir’in ayrı ayrı dışarıdan çağrıldığı yolda sınır iki kez açıldı.
  • yatirVeBildir içindeki yatir(miktar) iç çağrısı hiçbir kurulumda sınır sayısına eklenmedi; vekil yalnız dışarıdan gelen çağrıları görüyor.
  • İç çağrı this üzerinden yapıldığı için vekili hiç görmedi; @Islem açıklaması sınıfın üzerinde durmaya devam etse de bu çağrı için hiç okunmadı.
  • Kusur sessizdi: iç çağrı ne bir istisna fırlattı ne bir uyarı bastı, yalnız beklenen sınırı açmadan tamamlandı.
  • Vekil hiç kurulmadan çağrılan aynı sınıfta sınır bir kez bile açılmadı; sınırın çalışması açıklamanın varlığına değil, üç koşulun birlikte sağlanmasına bağlı.

Sonraki Adım

B payı burada kapanıyor. Bu ders sınırın nerede açıldığını bir açıklamanın söylediğini, ve içeriden gelen bir çağrının o sınırı hiç görmediğini gösterdi. Sıradaki konu aynı soruyu başka bir yerde soruyor: güvenlik, test ve dağıtık kurulumda da bir sınır — bu kez bir yetki sınırı — yine bir açıklamayla çiziliyor, ve içeriden gelen bir çağrının onu görüp görmediği yeniden ölçülü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