---
title: 'Çoklu Modül Projeler'
source: 'https://academia.sh/tr/kurslar/java-ekosistem/coklu-modul-projeler'
course: 'Derleme, Test ve Ekosistem'
language: tr
updated: '2026-08-23T14:25:06+00:00'
license: 'CC BY-SA 4.0'
---

# Çoklu Modül Projeler

Üç sınır kurulumundan biri derlemede, biri koşumda yakalanır, biri hiç yakalanmadan sınırı sessizce aşar. Sınırlayıcı ölçüm bunun neden böyle olduğunu gösterir: Genel ve Dahili aynı dizine derlenir, ve sınırı gerçekten kuran şey dizin değil bir yükleyicinin izin listesidir — aynı dizindeki iki sınıf dosyasından biri yüklenirken öbürü ClassNotFoundException ile reddedilir.

Önceki ders altı tekil tipin her birinin hangi sınıf yolunda gerekli olduğunu ölçtü — ama
her tip kendi başınaydı, tek bir sınıfın ya da arayüzün sınırları içinde kaldı. Gerçek bir
proje tek dosyalık sınıflardan değil, birbirine bağımlı **modüllerden** kurulur, ve bir
modülün "başkasına açık" saydığı yüzey ile "kendi içinde" tuttuğu yüzey arasındaki çizgi bir
dil kuralı değil, bir derleme kararıdır. Bu ders o çizginin derlemedeki karşılığını ölçüyor:
modül sınırı aşıldığında ne oluyor, ve bu yakalama nereden geliyor?

**Modül sınırı** burada bir çoklu modül projenin en küçük hâliyle kuruluyor: iki taraf,
biri "sağlayan" biri "tüketen". Sağlayan tarafın kendi içinde bir genel yüzeyi (`Genel`)
ve bir iç uygulaması (`Dahili`) var; tüketen taraf yalnız genel yüzeyi kullanmalı,
iç uygulamaya hiç dokunmamalı. Gerçek bir çoklu modül projede bu ayrım genellikle ayrı
dizinlere, ayrı derleme çıktılarına yansır — ama derlemenin kendisi bu ayrımı **neden**
uyguladığını hiç söylemez. Bu ders o nedeni ölçerek çıkarıyor.

## Modül Sınırının Derlemedeki Karşılığı

Bir modülün genel yüzeyi ile iç uygulaması aynı kaynak dizininden, aynı derleme çağrısıyla,
aynı çıktı dizinine derlenebilir — Java'nın kendisi bunları ayırmaz. Aşağıdaki `Genel` ve
`Dahili` sınıfları tam olarak böyle: ikisi de tek bir derleme çağrısında, tek bir çıktı
dizinine (`aTumOut`) yazılıyor. `Genel` dışarıya açık sayılan tarafı, `Dahili` yalnız
modülün kendi içinde kalması gereken tarafı temsil ediyor — ama bu ayrım şu ana kadar
yalnız bir isimlendirme kararı, derlemenin kendisi henüz hiçbir şey uygulamıyor.

- **DR14.** Üç sınır kurulumu da aynı iç tip (`Dahili.gizli`) üzerinden ölçülüyor; farklı
  olan yalnız erişim biçimi (doğrudan referans / yansıma) ve koşum sınıf yoluna hangi
  dizinin konduğu.
- **DR15.** `Genel` ve `Dahili` aynı kaynak dizininden aynı derleme çağrısıyla, aynı çıktı
  dizinine derleniyor; ikisi arasında fiziksel hiçbir ayrım yok.

Sınırı "gerçek" kılan adım, çıktının bir alt kümesini ayrı bir dizine kopyalamak: yalnız
`Genel.class`, `aGenelSadece` adlı seçilmiş bir dizine kopyalanıyor. Bir modülün
tüketicisine verilen sınıf yolu bu seçilmiş dizini gösteriyor, `Dahili`'nin de bulunduğu
tam çıktıyı değil.

```java
// ModulYardimci.java — bir kaynagi derleyip sinif yolu listesine karsi kosturmak icin ortak islemler
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.spi.ToolProvider;

public class ModulYardimci {
    static Path yaz(Path dizin, String ad, String kaynak) throws IOException {
        Files.createDirectories(dizin);
        Path p = dizin.resolve(ad + ".java");
        Files.writeString(p, kaynak);
        return p;
    }

    static boolean derle(Path cikti, List<Path> sinifYolu, Path... kaynaklar) throws IOException {
        Files.createDirectories(cikti);
        List<String> arg = new ArrayList<>(List.of("-d", cikti.toString()));
        if (!sinifYolu.isEmpty()) {
            StringJoiner sj = new StringJoiner(File.pathSeparator);
            for (Path p : sinifYolu) sj.add(p.toString());
            arg.add("-classpath");
            arg.add(sj.toString());
        }
        for (Path k : kaynaklar) arg.add(k.toString());
        ToolProvider derleyici = ToolProvider.findFirst("javac").orElseThrow();
        StringWriter sessiz = new StringWriter();
        int sonuc = derleyici.run(new PrintWriter(sessiz), new PrintWriter(sessiz), arg.toArray(new String[0]));
        return sonuc == 0;
    }

    static String kos(List<Path> sinifYolu, String anaSinif) throws Exception {
        URL[] url = new URL[sinifYolu.size()];
        for (int i = 0; i < url.length; i++) url[i] = sinifYolu.get(i).toUri().toURL();
        try (URLClassLoader yukleyici = new URLClassLoader(url, ModulYardimci.class.getClassLoader().getParent())) {
            Class<?> sinif = Class.forName(anaSinif, true, yukleyici);
            sinif.getMethod("main", String[].class).invoke(null, (Object) new String[0]);
            return "tamam";
        } catch (java.lang.reflect.InvocationTargetException e) {
            return e.getCause().getClass().getSimpleName();
        } catch (Throwable t) {
            return t.getClass().getSimpleName();
        }
    }
}
```

`derle`, önceki derslerdeki aynı işlemi yapıyor: kaynakları verilen bir sınıf yoluna karşı
derliyor. `kos` derlenmiş sınıfı ayrı bir sınıf yoluna karşı çalıştırıyor ve fırlayan
istisnanın sınıf adını döndürüyor — "tamam" değilse, sınırın çalışma zamanında yakalandığı
anlamına geliyor.

Bu iki yöntemin dışında `ModulYardimci` hiçbir şey bilmiyor — ne "genel" ne "iç" kavramını
tanıyor, ne bir modülün ne olduğunu. Sınırın kendisi bu sınıfın dışında, çağıran kodun
hangi dizinleri hangi sınıf yoluna kattığı kararında kuruluyor. Bu, bu dersin temel
gözlemini önceden özetliyor: modül sınırı bir dil özelliği değil, bir kullanım kararı.

## Üç Sınır Kurulumu

`Dahili.gizli` bir modülün iç uygulamasını temsil ediyor: dışarıdan çağrılması
"sınır aşımı". Aşağıdaki üç kurulum bu aşımı üç farklı biçimde deniyor; hepsi aynı `Dahili`
kaynağını, hepsi aynı `aGenelSadece` sınıf yolu tuzağını kullanıyor, ama biri kaynak
metninde bir tip adı yazarak, ikisi bir dizgi vererek erişiyor.

- **DR16.** "Yakalanmadı" sonucu yalnız kurulum 1'in derleme başarısızlığıyla
  karşılaştırılan bir referans değil; kurulum 3'te program hatasız çalışıyor ve iç tipin
  değeri okunuyor.
- **DR17.** Sınırlı yükleyici (`SinirliYukleyici`), JVM, Eşzamanlılık ve Başarım kursunun
  Sınıf Yükleme dersinde kurulan özel yükleyici düzeneğiyle aynı ilkeyi kullanıyor; yalnız
  bir izin listesini kendi bildirimine ekliyor.
- **DR18.** İzin listesi sınıf adına bakıyor, dizine bakmıyor; aynı dizindeki iki sınıf
  dosyasından biri yüklenebiliyor, öbürü `ClassNotFoundException` ile reddediliyor.
- **DR19.** `hesapla()` yöntemi, sınırlı yükleyicinin ölçümünde `Dahili` tipini hiç
  anmıyor; bu yüzden `Genel`'in yüklenip çalışması, `Dahili`'nin erişilebilir olup
  olmamasından bağımsız ölçülüyor.

```java
// SinirliYukleyici.java — ayni dizindeki sinif dosyalarindan yalniz izin verilen adlari tanimlayan yukleyici
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;

class SinirliYukleyici extends ClassLoader {
    private final Path dizin;
    private final Set<String> izinli;

    SinirliYukleyici(Path dizin, Set<String> izinli) {
        super(SinirliYukleyici.class.getClassLoader().getParent());
        this.dizin = dizin;
        this.izinli = izinli;
    }

    protected Class<?> findClass(String ad) throws ClassNotFoundException {
        if (!izinli.contains(ad)) throw new ClassNotFoundException(ad);
        try {
            byte[] bayt = Files.readAllBytes(dizin.resolve(ad + ".class"));
            return defineClass(ad, bayt, 0, bayt.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(ad);
        }
    }
}
```

```java
// ModulSiniri.java — modul sinirinin derlemedeki karsiligi: sinir bir sinif yolu ve yukleyici karari
import java.io.*;
import java.nio.file.*;
import java.util.*;

public class ModulSiniri {
    public static void main(String[] args) throws Exception {
        Path kok = Files.createTempDirectory("modul");
        Path kaynak = kok.resolve("kaynak");

        Path sDahili = ModulYardimci.yaz(kaynak, "Dahili",
                "public class Dahili { public static int gizli() { return 99; } }");
        Path sGenel = ModulYardimci.yaz(kaynak, "Genel",
                "public class Genel { public static int hesapla() { return 41; } }");

        Path aTumOut = kok.resolve("aTumOut");
        ModulYardimci.derle(aTumOut, List.of(), sDahili, sGenel);

        Path aGenelSadece = kok.resolve("aGenelSadece");
        Files.createDirectories(aGenelSadece);
        Files.copy(aTumOut.resolve("Genel.class"), aGenelSadece.resolve("Genel.class"));

        System.out.println("-- uc sinir kurulumu, kaci derlemede kaci kosumda yakalaniyor --");

        Path sB1 = ModulYardimci.yaz(kok.resolve("b1"), "B1",
                "public class B1 { public static void main(String[] a) { System.out.println(Dahili.gizli()); } }");
        boolean b1Derlendi = ModulYardimci.derle(kok.resolve("b1Cikti"), List.of(aGenelSadece), sB1);
        System.out.println("kurulum 1 - dogrudan referans, ic dizin sinif yolunda yok: derlendi mi = " + b1Derlendi
                + " -> " + (b1Derlendi ? "yakalanmadi" : "derlemede yakalandi"));

        Path sB2 = ModulYardimci.yaz(kok.resolve("b2"), "B2",
                "public class B2 { public static void main(String[] a) throws Exception {\n"
              + "  Class<?> c = Class.forName(\"Dahili\");\n"
              + "  c.getMethod(\"gizli\").invoke(null); } }");
        Path b2Cikti = kok.resolve("b2Cikti");
        boolean b2Derlendi = ModulYardimci.derle(b2Cikti, List.of(aGenelSadece), sB2);
        System.out.println("kurulum 2 - yansimayla erisim, ic dizin sinif yolunda yok: derlendi mi = " + b2Derlendi);
        String kurulum2Kosum = ModulYardimci.kos(List.of(b2Cikti, aGenelSadece), "B2");
        System.out.println("kurulum 2 kosum sonucu: " + kurulum2Kosum
                + " -> " + (kurulum2Kosum.equals("tamam") ? "yakalanmadi" : "kosumda yakalandi"));

        String kurulum3Kosum = ModulYardimci.kos(List.of(b2Cikti, aTumOut), "B2");
        System.out.println("kurulum 3 - ayni yansimali sinif, kosum sinif yoluna ic dizinin tamami eklendi: sonuc = "
                + kurulum3Kosum + " -> " + (kurulum3Kosum.equals("tamam") ? "hic yakalanmadi, sinir sessizce asildi" : "yakalandi"));

        System.out.println();
        System.out.println("-- sinirlayici olcum: sinir bir dizin ayrimi degil --");
        System.out.println("Genel ve Dahili ayni dizinde (aTumOut): " + Files.exists(aTumOut.resolve("Genel.class"))
                + ", " + Files.exists(aTumOut.resolve("Dahili.class")));
        SinirliYukleyici sinirli = new SinirliYukleyici(aTumOut, Set.of("Genel"));
        Class<?> genelSinif = Class.forName("Genel", true, sinirli);
        System.out.println("sinirli yukleyici Genel'i yukledi mi: true, hesapla() = "
                + genelSinif.getMethod("hesapla").invoke(null));
        String dahiliSonuc;
        try {
            Class.forName("Dahili", true, sinirli);
            dahiliSonuc = "yuklendi";
        } catch (ClassNotFoundException e) {
            dahiliSonuc = "ClassNotFoundException";
        }
        System.out.println("ayni sinirli yukleyici Dahili'yi yukleyebildi mi (ayni dizinde olmasina ragmen): " + dahiliSonuc);
    }
}
```

```
-- uc sinir kurulumu, kaci derlemede kaci kosumda yakalaniyor --
kurulum 1 - dogrudan referans, ic dizin sinif yolunda yok: derlendi mi = false -> derlemede yakalandi
kurulum 2 - yansimayla erisim, ic dizin sinif yolunda yok: derlendi mi = true
kurulum 2 kosum sonucu: ClassNotFoundException -> kosumda yakalandi
kurulum 3 - ayni yansimali sinif, kosum sinif yoluna ic dizinin tamami eklendi: sonuc = tamam -> hic yakalanmadi, sinir sessizce asildi

-- sinirlayici olcum: sinir bir dizin ayrimi degil --
Genel ve Dahili ayni dizinde (aTumOut): true, true
sinirli yukleyici Genel'i yukledi mi: true, hesapla() = 41
ayni sinirli yukleyici Dahili'yi yukleyebildi mi (ayni dizinde olmasina ragmen): ClassNotFoundException
```

İlk kurulum sınırı en katı biçimde ölçüyor: `B1`, `Dahili.gizli`'yi doğrudan tip
referansıyla çağırıyor, ve sınıf yolunda `Dahili` hiç yok. Derleyici sembolü çözemiyor,
derleme çöküyor — sınır **derlemede** yakalanıyor. İkinci kurulum aynı çağrıyı yansımayla
yapıyor: `B2` kaynağında `Dahili` adı hiçbir yerde doğrudan geçmiyor, bu yüzden derleme
sorunsuz tamamlanıyor. Ama koşum sınıf yolu da `Dahili`'yi içermiyor, ve `Class.forName`
çalışma zamanında `ClassNotFoundException` fırlatıyor — sınır bu kez **koşumda**
yakalanıyor.

Üçüncü kurulum aynı derlenmiş `B2` sınıfını değiştirmeden, yalnız koşum sınıf yoluna farklı
bir dizin ekleyerek çalıştırıyor: `aGenelSadece` yerine `aTumOut` — modülün tüm iç çıktısı.
Bu, gerçek bir projede sık rastlanan bir hata: birileri "yalnız genel arayüzü" değil, tüm
derleme çıktısını koşum sınıf yoluna koyuyor. Sonuç: program hatasız çalışıyor, `Dahili`
bulunuyor ve çağrılıyor — sınır **hiç yakalanmıyor**, sessizce aşılıyor.

**Üç sınır kurulumundan biri derlemede, biri koşumda yakalanıyor, biri hiç yakalanmıyor.**
Yakalamanın hangisi olduğunu belirleyen şey `B1`/`B2` kaynağının yazılışı değil, koşum
sınıf yoluna hangi dizinin konduğu.

Bu üçlünün sırası da anlamlı. En sıkı yakalama (derleme) en erken düşen yakalama —
derleyici sembolü çözemediği an duruyor, ekstra bir adım gerekmiyor. Orta sıkılıktaki
yakalama (koşum) bir adım daha esnek: kaynak derleniyor, program başlıyor, hata ancak
`Dahili`'ye gerçekten erişilmeye çalışıldığı satırda düşüyor — program o satıra hiç
uğramasaydı hata da hiç görünmezdi. En gevşek durum ise hiç yakalama değil: üçüncü
kurulumda kod hem derleniyor hem çalışıyor, ve `Dahili`'nin çağrılması hiçbir uyarı
üretmiyor. Modül sınırının ne kadar sıkı olduğu, bu üç kurulumun hangisine benzediğine
bağlı — ve bu bir dil garantisi değil, sınıf yoluna ne konduğuyla ilgili bir işletim
kararı.

## Sınırlayıcı Ölçüm: Sınır Bir Dizin Ayrımı Değildir

Yukarıdaki üç kurulum, sınırı ayrı dizinlere kopyalayarak kurdu — bu, gerçek bir yapı
sürecinin de yaptığı şey: yalnız genel yüzeyi ayrı bir çıktıya taşımak. Ama bu yönteme
güvenmek, sınırın **neden** işlediğini yanlış anlatır: sanki sınır fiziksel bir dizin
farkıymış gibi görünür. Son ölçüm bunun yanlış olduğunu gösteriyor.

`Genel` ve `Dahili` bu ölçümde hâlâ **aynı** dizinde duruyor — `aTumOut`, ikisinin de
sınıf dosyasını taşıyor, kopyalama yok. Sınırı burada kuran şey `SinirliYukleyici`'nin
kendi izin listesi: `Set.of("Genel")`. Bu yükleyici `Genel`i istendiğinde diskten okuyup
tanımlıyor — `hesapla()` çağrılabiliyor, `41` dönüyor. Ama aynı yükleyiciden `Dahili`
istendiğinde, `findClass` izin listesinde olmadığını görüp `ClassNotFoundException`
fırlatıyor; `Dahili.class` dosyası tam olarak aynı dizinde, birkaç bayt ötede duruyor,
ama yükleyici ona hiç bakmıyor bile.

Bu, önceki üç kurulumun ölçtüğü şeyi tersinden doğruluyor: modül sınırı bir **dizin
ayrımı** değil, bir **sınıf yolu ve yükleyici kararıdır**. Aynı dizinden derlenen iki tip
farklı sınıf yollarına (ya da farklı izin listelerine) konularak ayrı erişim düzeylerine
sahip olabiliyor, ve bu ayrım o zaman da işliyor — hiçbir fiziksel dizin ayrımı olmadan.
Kurulum 3'ün gösterdiği kaçış da bunun aynadaki yansıması: dizin ayrımı yeterli bir
güvence değil, çünkü sınırı taşıyan gerçek mekanizma her zaman hangi sınıf yolunun (ya
da hangi yükleyicinin) hangi adı görmesine izin verildiğidir.

Bu iki bulgu birlikte okunduğunda bir çoklu modül projenin sınırının nereye
yerleştirildiği ortaya çıkıyor: sınır ne kaynak dosyasının hangi klasörde durduğuna, ne
de sınıfın hangi paket adını taşıdığına bağlı. Sınır, bir sağlayan modülün derleme
çıktısından tüketen modüle hangi alt kümenin — hangi dizinin, hangi arşivin, hangi izin
listesinin — verildiğine bağlı. İki modül aynı depoda, aynı derleme çağrısıyla, hatta
aynı çıktı dizininde üretilebilir; sınırın gerçekten işlemesi için gereken tek şey,
tüketen tarafa verilen sınıf yolunun neyi içerip neyi içermediğidir.

## Özet

- Modül sınırının derlemedeki karşılığı bir sınıf yolu kararıdır: aynı kaynak dizininden
  derlenen "genel" ve "iç" tipler, tüketiciye farklı sınıf yolları verilerek ayrılır.
- Üç sınır kurulumundan biri derlemede yakalanır (doğrudan referans, tip sınıf yolunda
  yok), biri koşumda yakalanır (yansıma, tip koşum sınıf yolunda yok), biri hiç
  yakalanmaz (yansıma, ama koşum sınıf yoluna iç çıktının tamamı yanlışlıkla eklenmiş).
- Bir sınıf yükleyicinin izin listesi sınıf adına bakar, dizine bakmaz: aynı dizindeki iki
  sınıf dosyasından biri yüklenip çalıştırılabilirken öbürü `ClassNotFoundException` ile
  reddedilebilir.
- Sınırlayıcı ölçüm: `Genel` ve `Dahili` fiziksel olarak aynı dizinde durmaya devam eder;
  sınırı kuran şey dizin değil, yükleyicinin kendi kararıdır.

## Sonraki Adım

Bu derste bir modülün sınırı, koşum sınıf yoluna hangi dizinin ya da hangi yükleyicinin
konduğu üzerinden ölçüldü — sınırı biz kurduk, kararı biz verdik. Ama bu bildirim henüz
tek bir çalışan programa dönüşmedi; koşum sınıf yolu şu ana kadar hep dağınık dizinlerin
bir listesiydi. Sıradaki ders bu listeyi taşınabilir tek bir ürüne çeviriyor: derlenmiş
sınıflar bir arşive konuyor, giriş noktası kaynakta değil arşivin kendi bildiriminde
duruyor, ve arşivin bağımlılıklarını içerip içermediği ayrı bir soru olarak kalıyor.
