Java 9 – moduły

Pisząc klasy w typowej aplikacji Java (mam na myśli wersję 8) – finalnie prawdopodobnie pakujemy te pliki w archiwa jar. Kompilując czy uruchamiając program – musimy dodać do classpatha wszystkie wykorzystywane “jarki”. Czasem, gdy nie jesteśmy ostrożni może to skutkować tzw. “JAR Hell” – stanem, w którym wczytywanie klas nie przebiega prawidłowo. Java 9 wspiera programistę w tym, by uniknąć tego upokorzenia – wprowadzając modularyzację.

JAR Hell

Jeśli pominiemy (lub classloader z powodu naszego błędu pominie) jakąś bibliotekę podczas uruchamiania, prawdopodobnie w trakcie działania programu spotka nas NoClassDefFoundError. Błąd ten oznacza, że program próbuje wywołać klasę, której nie ma (ergo – której nie wczytał classloader).

Możemy też popełnić błąd tego typu, że dwie te same biblioteki o różnych nazwach (np. o różnych wersjach) dołączymy do classpath. Wtedy klasy załadują się z jednej lub drugiej z nich – zależnie od tego, która będzie pierwsza (tzw. zjawisko shadowingu). Java widzi swoje składowe jako zwyczajne kontenerki z klasami. Nie zwraca uwagi na takie pierdoły jak nazwa bibliotek lub ich wersja, czy zależności między nimi. Jeśli ma wczytać klasę Abc z pakietu x.y.z, a w programie dostępne są takie dwie – to wczyta jedną. Tę, którą znajdzie szybciej.

Trzeci problem to dostępność klas. Klasy z modyfikatorem public są widoczne nie tylko wewnątrz biblioteki, ale także wszystkim innym bibliotekom i programom korzystającym z naszego pliku jar. Oznacza to słabą enkapsulację.

RT.jar

Przed Javą 9 Java Runtime zawierała pewną paczkę zwaną rt.jar. Ta jarka zawierała wszystkie podstawowe klasy Javy, a sam plik rósł od momentu powstania, do rozmiaru ponad 60 MB. Aby uruchomić aplikację java, należało dołączać do swojego programu cały zestaw klas, często nieużywanych, typu obsługa xml, sql, biblioteki swing itd.

Modularyzacja

Problemy, które mnożyły się z powodu JAR Hell i pliku rt.jar spowodowały, że od Javy 9 zamiast wielkiego rt.jar mamy dostępne 95 mniejszych części tego kombajnu – modułów. Każdy z modułów enkapsuluje pewien fragment dawnego rt.jar – teraz podzielonego według funkcjonalności, jak java.logging, java.xml, java.sql itd.

Modułem nadrzędnym jest java.base – zawierający typy proste, podstawowe obiekty itd. Wszystkie inne moduły są od niego zależne, a w schemacie modułowym javy nie ma cykli.

Czym jest moduł?

Moduł w Javie 9+ jest określony jako zestaw kodu i danych. Kod to oczywiście klasy javy, a dane – zasoby, z których te klasy korzystają. Wszystkie dostępne w naszej aplikacji klasy, interfejsy, enumy itd zebrać w moduł.

Przeanalizujmy moduł java.sql uruchamiając na nim komendę describe module:

> java --describe-module java.sql

java.sql@16.0.1
exports java.sql
exports javax.sql
requires java.transaction.xa transitive
requires java.logging transitive
requires java.base mandate
requires java.xml transitive
uses java.sql.Driver

exports
widzimy, że w opisie modułu java.sql znajduje się słowo kluczowe exports. Oznacza to, że moduł eksportuje pakiety java.sql i javax.sql. Pokazuje to kompilatorowi (i w sumie programiście też), że klasy z pakietów java.sql i javax.sql dostępne w tym module będą widoczne dla klas spoza modułu. Klasy z pozostałych pakietów, nawet jeśli będą miały modyfikatory dostepu ustawione na public – nie będą dostępne. Jest to jednoznaczne z silną enkapsulacją charakterystyki modułowej Javy 9.

deskryptor modułu

Plik, który opisuje dany moduł to deskryptor modułu, plik powinien nazywać się module-info.java. Musimy utworzyć go w katalogu src/main/java, a jego struktura jest następująca (dla java.sql):

module java.sql{
    exports java.sql;
    exports javax.sql;
    requires java.transaction;
    requires java.base;
    ...
    ...
}

Kompilacja classpath a modulepath

Classpath to informacja dla JVM gdzie powinien szukać skompilowanych klas w trakcie wykonywania programu. Zawiera on zestaw katalogów, plików jar, archiwów, w których znajdują się skompilowane klasy. Kiedy aplikacja pracuje, wykorzystuje classpach, żeby zlokalizować odpowiednią klasę i zasoby potrzebne do wykonywania konkretnych operacji. Modulepath jest tym samym – jednak dla modułów.

CLASSPATH MODULEPATH
Obsługuje zależności na poziomie klas Obsługuje zależności na poziomie całych modułów
Nie wymaga deklaracji zależności innej niż import klasy Wymaga sztywną deklarację zależności do innych modułów
Wszystko jest dostępne publicznie Silna enkapsulacja
Może skutkować naming conflictem Zapobiega niechcianym deklaracjom

Moduły anonimowe i automatyczne

Skoro JVM szuka modułów (mówimy o kompilacji typu modulepath) to i naszą aplikację traktuje jako moduł. A co w przypadku, gdy nie będziemy mieli pliku module-info?

unnamed module

Przydatne w przypadku migracji aplikacji napisanych w starszych wersjach Javy do Javy 9 jest istnienie tzw. modułów anonimowych (Unnamed module). Polega to na tym, że podczas kompilacji z użyciem classpath w javie 9 moduł tworzony jest w automatyczny sposób. Dzięki temu, że aplikacja jest modułem, może korzystać ze wszystkich innych modułów jej potrzebnych i nie będzie tutaj miejsca na problemy z uruchomieniem aplikacji ze względu na wersję Javy.

automatic module

Pewnym uzupełnieniem modułu unnamed jest moduł automatyczny. Jest on tworzony w ten sam sposób co unnamed module, jednak w tym przypadku należy wykorzystać kompilację typu modulepath. Nazwa modułu będzie jednoznaczna z nazwą pliku jar, który zawiera aplikację i tak jak w przypadku unnamed module – wszystkie jego pakiety będą wyeksportowane, a więc publiczne klasy z tych pakietów będą dostępne na zewnątrz.

Jaka jest więc różnica?

Główna różnica między modułem anonimowym a automatycznym jest związana z jego nazwą. Moduł anonimowy nie ma nazwy – wywołanie metody getName() na tym module zwróci nulla. Nie jest więc możliwe zaimportowanie go do innych modułów. Moduł automatyczny ma nazwę taką samą jak plik jar, z którego zostanie zrobiony. Mając nazwę jest pełnoprawnym modułem, może być więc required w innych modułach.

Umożliwia to budowanie aplikacji modułowej z wykorzystaniem third party libraries, nawet w sytuacji, gdy nie możemy rozwinąć już istniejącego, zewnętrznego kodu (choćby z powodów licencyjnych), lub dostawca nie przewiduje uzupełnienia biblioteki o modularyzację.

Zalety modularyzacji

Jak w przypadku budowania z użyciem narzędzi automatyzacji builda – maven czy gradle – podczas kompilacji jvm poszukuje odpowiednich modułów do budowy aplikacji. Gdy będzie brakować modułów – aplikacja sie nie skompiluje / nie uruchomi. Podobnie będzie w przypadku, gdy będziemy próbowali wykorzystywać klasy, które są w niepublicznych pakietach. Informacja o braku jakichś klas jest już w momencie uruchamiania aplikacji, nie zaś dopiero w momencie, gdy aplikacja działa już jakiś czas.

Główną zaletą modularyzacji natomiast jest dodanie silnej enkapsulacji do javy – teraz możliwe jest zatrzymanie w środku modułu klas, które mimo publicznego modyfikatora dostępu nie powinny być widoczne na zwenątrz.

Modularyzacja dzięki temu pozwala poprawić bezpieczeństwo i łatwość utrzymania aplikacji. Krytyczne sekcje kodu (nie sekcje krytyczne dot. wielowątkowości) mogą być teraz ukryte, nawet, jeśli publiczny modyfikator dostępu jest im potrzebny np. po to, żeby były widoczne na przestrzeni całego modułu. Dzięki poszatkowaniu rt.jar jesteśmy też w stanie umiejętnie skalować rozmiar aplikacji, dodając do niej tylko to, co jest potrzebne.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Strona używa danych zapisanych na komputerze odwiedzającego. Kliknij przycisk AKCEPTUJ COOKIES, aby nie widzieć więcej tego komunikatu Polityka prywatności

Gniado IT wykorzystuje cookies do przechowywania informacji na TWOIM komputerze. Cookies wykorzystywane przez tę witrynę to standardowe cookies wykorzystywane przez silnik Wordpress. Możesz ustawić swoją przeglądarkę w ten sposób, aby blokowała wszelkie próby użycia cookies, jednakże może to skutkować nieprawidłowym działaniem witryny. Dane nie są wykorzystywane do profilowania reklamowego, czy pozyskania wrażliwych / poufnych informacji.

Zamknij