CodeGym /Kursy /JAVA 25 SELF /Wprowadzenie do modułów: po co są potrzebne

Wprowadzenie do modułów: po co są potrzebne

JAVA 25 SELF
Poziom 60, Lekcja 0
Dostępny

1. Problemy starego podejścia

„Classpath party”: kiedy wszyscy w jednej kuchni

Przed pojawieniem się modułów w Javie (w wersji dziewiątej) cały kod aplikacji, biblioteki i zależności trafiały do jednej wielkiej sterty — classpath. Granic między bibliotekami praktycznie nie było: każda klasa, która trafiła do classpath, mogła zostać znaleziona i użyta przez kogokolwiek.

Pojawiały się konflikty nazw: dwie biblioteki zawierają klasę o tej samej w pełni kwalifikowanej nazwie, na przykład com.example.Util — wybierano „jakąś jedną”, a o problemie dowiadywaliście się dopiero po dziwnym zachowaniu w czasie działania.

Klasyka gatunku — dependency hell: jedna biblioteka wymaga loggera w wersji 1.2, inna — 1.3, a w classpath lądują obie. JVM nie wie, czego użyć, i trafia się zagadkowy NoSuchMethodError.

I jeszcze: nawet jeśli klasa jest oznaczona jako public, ale przeznaczona do wewnętrznych potrzeb biblioteki, inny kod wciąż może jej użyć — i przy okazji coś sobie zepsuć. Duże projekty zamieniały się w pole minowe, a utrzymanie — w przygodę.

2. Czym jest moduł w Javie?

Moduł: jak pudełko z otworkami

W Java 9 pojawił się system modułowy (JPMS). Moduł to pudełko z klasami i pakietami, w którym sami robicie „otworki”: wprost wskazujecie, co pokazujecie na zewnątrz (exports), a co ukrywacie w środku. I tak: nawet klasa public nie jest widoczna dla innych modułów, jeśli jej pakiet nie jest eksportowany.

Definicja formalna

Moduł — logiczna jednostka podziału kodu, łącząca powiązane pakiety i klasy. Każdy moduł jawnie deklaruje:

  • co eksportuje — udostępnia innym modułom (exports);
  • oraz co importuje — od jakich innych modułów zależy (requires).

Kluczowe właściwości modułu:

  • Jawne granice. Jasno określone, co jest dostępne na zewnątrz, a co nie.
  • Jawne zależności. Moduł nie „widzi” innego modułu bez jawnego requires.
  • Izolacja. Wewnętrzne szczegóły mogą być całkowicie ukryte przed światem zewnętrznym.

3. Zalety modułowości

Lepsza enkapsulacja

Pojawił się nowy poziom widoczności — na poziomie modułu. Nawet jeśli klasa jest public, ale jej pakiet nie jest eksportowany, jest widoczna tylko wewnątrz modułu. Wnętrzności biblioteki już nie „przeciekają” na zewnątrz.

Jasny opis zależności

Wymagane zależności są wymienione w module-info.java. Kompilator i IDE zawczasu ostrzegą, jeśli zapomnieliście wskazać moduł, od którego zależycie.

Lepsze bezpieczeństwo i niezawodność

Ograniczenie dostępu do wewnętrznych API utrudnia przypadkową lub celową ingerencję. To krytyczne dla dużych bibliotek i modułów platformy.

Możliwość tworzenia „okrojonego” JRE

Za pomocą jlink można złożyć minimalne środowisko uruchomieniowe tylko z potrzebnych modułów. W chmurze i scenariuszach wbudowanych (embedded) oszczędza to miejsce na dysku i pamięć.

Bonus: szybszy start i mniejszy rozmiar

Ładowane są nie wszystkie moduły, a jedynie faktycznie używane — aplikacje startują szybciej i zużywają mniej zasobów.

4. Gdzie używa się modułów

W standardowej bibliotece Javy

Począwszy od Java 9, sama platforma jest modułowa. Przykłady:

  • java.base — moduł podstawowy (wymagany dla każdego programu).
  • java.sql — obsługa baz danych.
  • java.xml — obsługa XML.

Jeśli nie korzystasz z XML, moduł java.xml nawet nie trafi do twojego środowiska uruchomieniowego.

W dużych aplikacjach i bibliotekach

Must-have dla systemów korporacyjnych, gdzie dziesiątki zespołów rozwijają części monorepozytorium. Modułowość zmniejsza konflikty i upraszcza utrzymanie.

We własnych projektach

Nawet w projekcie hobbystycznym moduły pomagają ćwiczyć myślenie architektoniczne i utrzymywać porządek w kodzie, unikając „spaghetti”.

5. Krótki przegląd składni: module-info.java

Najważniejszy jest plik module-info.java

Plik module-info.java leży w katalogu głównym źródeł modułu i deklaruje jego granice oraz zależności.

module my.awesome.module {
    exports com.example.api;        // Eksportowany pakiet
    requires java.sql;              // Zależność od standardowego modułu
}

Podstawowe słowa kluczowe:

  • module <nazwa> — deklaruje moduł.
  • exports <pakiet> — udostępnia pakiet innym modułom.
  • requires <moduł> — deklaruje zależność od innego modułu.

Minimalny przykład:

module com.myproject.core {
    exports com.myproject.core.api;
}

Przykład z zależnością:

module com.myproject.app {
    requires com.myproject.core;
    requires java.sql;
}

Dodatkowe możliwości (w skrócie): do refleksji służy opens, do usług — uses i provides ... with ... (szczegóły — w zaawansowanych wykładach).

6. Przydatne niuanse

Moduły są jak „paszporty” dla klas. Wcześniej każda klasa z wizą public mogła „podróżować” po aplikacji. Teraz potrzebny jest jeszcze „paszport” modułowy — eksport pakietu (exports). Bez niego klasa zostaje „w domu”.

Dependency hell to prawdziwy termin. Jeśli widzieliście ClassNotFoundException: com.google.common.base.Strings, byliście tam. Moduły mają przepędzić te „diabły” dzięki ścisłym granicom i zależnościom.

Cała Java jest teraz modułowa. Nawet jeśli nie piszecie własnych modułów, platforma jest już podzielona na dziesiątki. Wypróbujcie polecenie:

java --list-modules

Jak to wpływa na rozwój oprogramowania

Jawnie kontrolujecie widoczność kodu i wewnętrzne pakiety nie stają się dostępne wszystkim „przez przypadek”. IDE i kompilator wyłapują błędy wcześniej: zapomnieliście zadeklarować zależność — projekt się nie zbuduje. Utrzymanie dużych systemów się upraszcza: łatwiej zrozumieć, kto od kogo zależy i co może się zepsuć przy zmianach.

7. Typowe błędy przy przejściu na moduły

Błąd nr 1: zapomnieliście wyeksportować pakiet, ale klasa jest public. Zadeklarowaliście klasę jako public, ale nie wyeksportowaliście jej pakietu — inne moduły nie będą mogły jej użyć. Kompilator zgłosi: „Pakiet nie jest eksportowany przez moduł”.

Błąd nr 2: nie zadeklarowano zależności przez requires. Używacie typu z innego modułu, ale zapomnieliście dodać requires do module-info.java. W efekcie — błąd kompilacji „moduł nie może znaleźć wymaganej klasy”.

Błąd nr 3: zduplikowane nazwy modułów. W dużym projekcie dwa moduły przypadkowo otrzymały tę samą nazwę. JVM tego nie wybacza — zmieńcie nazwę i trzymajcie się spójnego nazewnictwa.

Błąd nr 4: zapomniano o standardowych modułach. Na przykład używacie JDBC, ale nie dodaliście requires java.sql;. W Javie 8 „i tak było widać”, a w 9+ — już nie.

Błąd nr 5: próba użycia wewnętrznej klasy innego modułu. Jeśli pakiet nie jest eksportowany, nawet klasa public pozostaje niewidoczna z zewnątrz. Eksportujcie pakiet albo wynieście API do „publicznej” warstwy.

1
Zadanie
JAVA 25 SELF, poziom 60, lekcja 0
Niedostępne
Minimalne serce modułu 🫀
Minimalne serce modułu 🫀
1
Zadanie
JAVA 25 SELF, poziom 60, lekcja 0
Niedostępne
Otwarcie bramy do biblioteki 🔑
Otwarcie bramy do biblioteki 🔑
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION