Java 9: praktyczny przykład - trzy moduły
Programowanie modularne
Dziś szybki “przykład na kolanie” pokazujący jak napisać aplikację modularną. Przykładowa aplikacja będzie generatorem obrazów (bggen). Architektura modułów będzie bardzo prosta. Oto ona:
- moduł
apizwiera zbiór interfejsów i klas pomocniczych - moduł
generatorpełni funkcję biblioteki, potencjalnie jednej z wielu, która implementuje interfejsy zdefiniowane wapi - moduł
clientużywa moduługeneratorna swoje potrzeby biznesowe.
Krok po kroku
Opiszę krok po kroku jak stworzyć taką aplikację.
Utworzenie projektu z trzema modułami
Używam edytora IntellijIdea Community. Tworzę standardowy projekt (javowy, nie używam mavena), a wewnątrz niego dodaję trzy moduły (i usuwam moduł “główny” utworzony automatycznie po utworzeniu aplikacji; chę mieć płaską strukturę projektu). Są to moduły w rozumieniu IntellijIdea - mają odrębne struktury katalogów, ustawienia SDK, bibliotek, katalogu wynikowego kompilacji itd.
Implementacja
apidefiniuje interfejsRectangleGenerator, którego jedyną funckją jest dostarczenie strumienia prostokątów (klasaRect)generatorposiada prywatne implementacje tego interfejsu i udostępnia je jedynie przez metody static factory klasyGenerators.clientbędzie używał wygenerowanych przez dostępne implementacjeRectangleGeneratora prostokątów i tworzył obrazy o zadanej rozdzielczości (w przykładzie prostokąty są wypisywane na standardowe wyjście).
Definiowanie modułów w rozumieniu java 9
Moduły w rozumieniu IntelliJIdea staną się modułami javy 9 tylko wówczas, gdy będą posiadały deskryptory modułu. Deskryptor modułu jest skompilowanym opisem modułu. Opis modułu to plik o nazwie module-info.java.
Wewnątrz katalogów src każdego z modułów tworzę opisy, z których wynika, że:
apieksportuje pakietcom.kamilachyla.bggen.api:
module api {
exports com.kamilachyla.bggen.api;
}generatorużywa modułuapii eksportujecom.kamilachyla.bggen.generator:
module generator {
requires api;
exports com.kamilachyla.bggen.generator;
}clientposiada jedną klasę (Main); używa moduługeneratori klas/interfejsów z api:
module client {
requires generator;
requires api;
}Struktura plików na dysku wygląda teraz tak:
api
│ ├── api.iml
│ └── src
│ ├── com
│ │ └── kamilachyla
│ │ └── bggen
│ │ └── api
│ │ ├── Rect.java
│ │ └── RectangleGenerator.java
│ └── module-info.java
├── bggen.iml
├── client
│ ├── client.iml
│ └── src
│ ├── com
│ │ └── kamilachyla
│ │ └── Main.java
│ ├── META-INF
│ │ └── MANIFEST.MF
│ └── module-info.java
└── generator
├── generator.iml
└── src
├── com
│ └── kamilachyla
│ └── bggen
│ └── generator
│ ├── Generators.java
│ └── types
│ ├── SimpleGenerator.java
│ └── SquaresGenerator.java
└── module-info.javaKompilacja
Kompiluję trzy moduły w taki oto sposób:
- tworzę katalog mlib, gdzie będą się znajdowały klasy wynikowe (mlib będzie katalogiem zawierającym skompilowane moduły)
- kompiluję - niezależny od innych modułów - moduł
api:
javac -d mlib/api $(find api -name "*.java")- kompilacja modułów zależnych od innych modułów wymaga podania ścieżki do katalogu z wymaganymi modułami, więc do skompilowania modułów
generatororazclientużywam opcji wiersza poleceń--module-path mlib:
$ javac --module-path mlib -d mlib/generator $(find generator -na
me "*.java")
$ javac --module-path mlib -d mlib/client $(find client -name "*.
java")Gotowe! Oto struktura katalogu mlib ze skompilowanymi modułami:
bbgen
├── api
│ ├── com
│ │ └── kamilachyla
│ │ └── bggen
│ │ └── api
│ │ ├── Rect.class
│ │ └── RectangleGenerator.class
│ └── module-info.class
├── client
│ ├── com
│ │ └── kamilachyla
│ │ └── Main.class
│ └── module-info.class
└── generator
├── com
│ └── kamilachyla
│ └── bggen
│ └── generator
│ ├── Generators.class
│ └── types
│ ├── SimpleGenerator.class
│ └── SquaresGenerator.class
└── module-info.classUruchomienie
Uruchomienie aplikacji również wymaga podania ścieżki do katalogu z modułami (program java również rozpoznaje opcję --module-path), a samą klasę główną podaję poprzedzoną nazwą modułu w postaci <nazwa_modułu>/<pełna-nazwa-klasy>:
java --module-path mlib -m client/com.kamilachyla.MainI w rezultacie otrzymuję:
Generates single Rect with x=0, y=0 and given width and height
Rect[x=0.0, y=0.0, width=800.0, height=600.0]
Generates stream of squares with its side not smaller than 20
Rect[x=0.0, y=0.0, width=600.0, height=600.0]
Rect[x=600.0, y=0.0, width=200.0, height=200.0]
Rect[x=600.0, y=200.0, width=200.0, height=200.0]
Rect[x=600.0, y=400.0, width=200.0, height=200.0]Generowanie wykresu zależności między modułami i pakietami
Narzędzie jdeps pozwala wygeerować graf zależności między pakietami/modułami w formacie .dot, z którego graphviz generuje wykres.
mkdir dot
jdeps --dot-output dot mlib/
dot -Tpng dot/mlib.dot -ojava-9-jdeps-small.png
Ponwyższy wykres wygenerowałam bez zależności do pakietów z biblioteki standardowej; dla ciekawych - pełny wykres:

Źródła projektu dostępne są w projekcie bggen na githubie.
This text is part of the series java...
- 2025-21-03 - Java 24 Overview
- 2024-01-11 - Java 23 w praktyce
- 2021-27-11 - JDK 18 proposals: Java 18
- 2021-21-09 - Java 17 - RandomGenerator i spółka
- 2021-15-09 - Java 17 - co nowego?
- 2021-04-03 - Java 15 - czym są sealed classes?
- 2021-26-02 - Java 13 i 14: Bloki tekstowe i rekordy
- 2021-24-02 - Java 12 - wyrażenie switch (preview feature)
- 2021-23-02 - Java 11 - HTTP Client i uruchamianie jednoplikowych programów
- 2021-18-02 - Java 9 - co to jest JShell i dlaczego warto używać REPL-a w Javie
- 2021-12-02 - Java 10 - var, nowe metody w Optional, kolekcje "unmodifiable"
- 2021-11-02 - Java 9 - nowości w bibliotece
- 2021-10-02 - Java 9: czy mogę stworzyć z mojej aplikacji binarkę?
- 2021-10-02 - Java 9: praktyczny przykład - trzy moduły
- 2021-08-02 - Java 8: praktyczny przykład - przewidywanie kolejnej daty w serii