TL;DR: Java 9 wprowadza system modułów (Project Jigsaw/JPMS), który pozwala na lepszą enkapsulację kodu i zarządzanie zależnościami. Kluczowe elementy to plik module-info.java, dyrektywy exports/requires oraz nowe narzędzia jak jlink.
Dlaczego modularność w Java 9 to przełom?
System modułów w Java 9 rozwiązuje problemy, z którymi programiści borykają się od lat. Obecnie każdy jar jest dostępny dla całej aplikacji, co prowadzi do „JAR hell” – konfliktów wersji bibliotek. Modularność wprowadza kontrolowaną widoczność i wymuszone deklarowanie zależności, co znacznie poprawia bezpieczeństwo i maintenance kodu.
Modularność nie jest obowiązkowa – istniejący kod Java 8 będzie działał na Java 9 bez zmian. To backward compatible enhancement.
Co się nauczysz:
Jak tworzyć moduły Java za pomocą module-info.java
Używanie dyrektyw exports, requires i provides
Migracja istniejących projektów do modularności
Narzędzia jdeps i jlink w praktyce
Best practices dla modularnych aplikacji
Wymagania wstępne: Znajomość Java 8, podstawy Maven/Gradle, doświadczenie z większymi projektami gdzie zarządzanie zależnościami było problematyczne.
System modułów Java (JPMS) – podstawowe koncepcje
Java Platform Module System (JPMS) wprowadza nowy poziom abstrakcji powyżej pakietów. Moduł to pojemnik na powiązane pakiety i zasoby, który:
Enkapsuluje implementację – tylko wyeksportowane pakiety są dostępne
Deklaruje zależności – wymuszone określenie wymaganych modułów
Zapewnia silne enkapsulację – reflection nie omija granic modułów
Moduł – nazwa jednostka deploymentu zawierająca kod Java oraz metadane opisujące zależności i eksportowane API.
Struktura pliku module-info.java
Każdy moduł musi zawierać plik module-info.java w głównym katalogu źródeł:
// src/main/java/module-info.java
module com.mycompany.userservice {
// Eksportujemy pakiety dostępne dla innych modułów
exports com.mycompany.userservice.api;
exports com.mycompany.userservice.dto;
// Wymagamy innych modułów
requires java.base; // automatycznie dodawane
requires java.sql;
requires com.mycompany.commons;
// Opcjonalne zależności
requires static lombok; // compile-time only
requires transitive gson; // propaguje zależność
}
Pro tip: Używaj requires transitive gdy twój moduł wymaga, żeby klienci mieli dostęp do danej zależności. To jak „re-export” w innych językach.
Tworzenie pierwszego modułu
Stwórzmy praktyczny przykład modularnej aplikacji z dwoma modułami:
Moduł commons (współdzielone utility)
// commons/src/main/java/module-info.java
module com.example.commons {
exports com.example.commons.utils;
exports com.example.commons.model;
}
// commons/src/main/java/com/example/commons/utils/StringUtils.java
package com.example.commons.utils;
public class StringUtils {
public static boolean isNotEmpty(String str) {
return str != null && !str.trim().isEmpty();
}
// Ta metoda NIE będzie dostępna z zewnątrz
// bo pakiet internal nie jest exported
static void internalMethod() {
System.out.println("Internal utility");
}
}
Moduł application (główna aplikacja)
// app/src/main/java/module-info.java
module com.example.application {
requires com.example.commons;
requires java.base; // implicit, ale można dodać explicit
// Ten moduł nie eksportuje niczego - to aplikacja końcowa
}
// app/src/main/java/com/example/application/Main.java
package com.example.application;
import com.example.commons.utils.StringUtils;
public class Main {
public static void main(String[] args) {
String input = "Hello Java 9 Modules!";
if (StringUtils.isNotEmpty(input)) {
System.out.println("Input is valid: " + input);
}
// To nie skompiluje się - metoda internal nie jest dostępna
// StringUtils.internalMethod(); // BŁĄD KOMPILACJI
}
}
Uwaga: Gdy używasz modułów, refleksja nie może dostać się do niepublicznych elementów z innych modułów. To znacząca zmiana dla niektórych frameworków.
Narzędzie jdeps pomaga w analizie istniejącego kodu i przygotowaniu do modularyzacji:
# Sprawdź zależności JAR-a
jdeps myapp.jar
# Sugerowane moduły dla istniejącego kodu
jdeps --generate-module-info . myapp.jar
# Sprawdź czy kod korzysta z internal JDK APIs
jdeps --jdk-internals myapp.jar
jlink – tworzenie runtime image
Nowe narzędzie jlink pozwala tworzyć minimalne runtime obrazy:
# Tworzenie custom runtime z tylko potrzebnymi modułami
jlink --module-path $JAVA_HOME/jmods:lib \
--add-modules com.example.application \
--output myapp-runtime \
--launcher myapp=com.example.application/com.example.application.Main
# Rezultat - folder z kompletną, minimalną JVM + aplikacja
./myapp-runtime/bin/myapp
Pro tip: Runtime image utworzony przez jlink może być 5-10 razy mniejszy od standardowej JVM, co znacznie przyspiesza deployment w kontenerach.
Migracja istniejących projektów
Strategia postupowa migracji
Nie musisz modularyzować całej aplikacji od razu. Java 9 wspiera trzy rodzaje „modułów”:
Named modules – mają module-info.java
Automatic modules – JAR-y na module path bez module-info
Spring planuje stopniowe wprowadzenie wsparcia dla modułów. Obecnie Spring Boot 1.x będzie działał jako automatic module.
Hibernate i JPA
ORM libraries będą musiały dostosować się do ograniczeń reflection w modularnym środowisku.
Uwaga: Niektóre biblioteki używające reflection do prywatnych pól mogą przestać działać. Sprawdź kompatybilność przed migracją.
Czy muszę modularyzować istniejący kod Java 8?
Nie, modularność jest opcjonalna. Istniejący kod Java 8 będzie działał na Java 9 bez zmian jako „unnamed module”. Modularyzacja to długoterminowa inwestycja w architekturę.
Jak module-info.java wpływa na wydajność?
Modularność może poprawić wydajność przez eliminację niepotrzebnych zależności podczas kompilacji i runtime. JVM może też lepiej optymalizować modularny kod.
Co to są automatic modules?
JAR-y bez module-info.java umieszczone na module path stają się automatic modules. Ich nazwa modułu jest wygenerowana automatycznie z nazwy JAR-a. Eksportują wszystkie pakiety i wymagają wszystkich innych modułów.
Czy mogę mieszać moduły z classpath?
Tak, Java 9 wspiera stopniową migrację. Kod na classpath działa jako unnamed module i może używać modularnego kodu, ale nie na odwrót.
Jak debugging modularnych aplikacji?
Używaj flag JVM: --show-module-resolution do debugowania ładowania modułów, --illegal-access=debug do wykrywania problemów z enkapsulacją.
Jakie są rozmiary runtime image z jlink?
Minimalna aplikacja z base module zajmuje ~40MB vs ~200MB standardowego JRE. Aplikacje Spring Boot mogą zredukować rozmiar z 150MB do 80MB.
Czy IDE wspierają moduły Java 9?
IntelliJ IDEA 2017.1+ i Eclipse Oxygen będą wspierać moduły. Aktualnie (grudzień 2016) wsparcie jest w fazie beta/early access.
🚀 Zadanie dla Ciebie
Stwórz modularną aplikację składającą się z trzech modułów:
common – zawiera klasę Logger z metodą log(String message)
calculator – używa common, eksportuje klasę Calculator z metodami add/subtract
app – używa calculator i common, zawiera main method
Dodaj odpowiednie pliki module-info.java i przetestuj kompilację z javac oraz uruchomienie z java.