Lekce 2 - Classloader a první modul v Javě
V předchozí lekci, Úvod do modulů v Javě, jsme se seznámili se strukturou a vlastnostmi modulů a ukázali si, jak pomáhají řešit problematiku závislosti balíčků.
V této lekci budeme ještě chvilku pokračovat v teorii. V dnešním Java tutoriálu si povíme, jak funguje classloader a seznámíme se s různými typy modulů. Na závěr si ukážeme, jak takový modul vypadá.
Načítání tříd
O načítání tříd v aplikaci se stará tzv. classloader. V Javě existují celkem tři druhy classloaderů:
- Bootstrap classloader - základní classloader,
naimplementovaný přímo v C++ (nikoliv v Javě), starající se o načtení
kritických modulů, jako je
java.base
, - Platform classloader - tento classloader je zodpovědný za
načítání všech nekritických modulů, které ale jsou stále součástí
JRE, jako je
java.sql
, - System (Application) classloader - poslední classloader načítá všechny ostatní moduly, typicky se jedná o námi vytvořené moduly a veškeré knihovny třetích stran.
Výčet modulů, které se načítají Bootstrap a Platform classloaderem je možné dohledat v dotazu ze Stack Overflow.
Hierarchie těchto tří classloaderů je stejná, jako v našem výčtu. Platform má jako svého rodiče nastavený Bootstrap a sám je rodičem System (Application). Bootstrap žádného rodiče nemá.
Načítání tříd má jasně definované flow, které můžeme vidět na obrázku níže:
Pojďme si jednotlivé kroky blíže popsat:
- Na začátku je požadavek pro načtení třídy.
- Zkontroluje se, zdali se už třída někdy v minulosti nenačítala, pokud ano, vrátí se z cache (tento bod není v grafu).
- Nejprve se požadavek dostane k System (Application) classloaderu, který ho rovnou deleguje na svého rodiče - Platform classloader.
- Platform classloader opět rovnou deleguje požadavek na svého rodiče - Bootstrap classloader.
- Bootstrap classloader žádného rodiče nemá, takže se podívá, jestli je schopný požadovanou třídu načíst. Pokud ano, třídu vrátí. Pokud ne, vrátí se vyhledávání třídy zpět do Platform classloaderu.
- Platform classloader se nyní už podívá, jestli je schopný třídu načíst. Pokud ano, třídu vrátí. Pokud ne, vrátí vyhledávání třídy zpět do System (Application) classloaderu.
- System (Application) classloader je poslední, kdo může
třídu načíst. V případě nezdaru se vyhodí velmi známá a nepříjemná
výjimka
ClassNotFoundException
.
V ukázce níže uvidíte po spuštění kódu, že různé třídy opravdu vrací různé classloadery:
{JAVA_CONSOLE}
System.out.println(System.Logger.class.getClassLoader());
System.out.println(java.sql.SQLPermission.class.getClassLoader());
System.out.println(Program.class.getClassLoader());
{/JAVA_CONSOLE}
Výstup:
Konzolová aplikace
null
jdk.internal.loader.ClassLoaders$PlatformClassLoader@273f6b01
jdk.internal.loader.ClassLoaders$AppClassLoader@42110406
Classloadery hrají důležitou roli v kombinaci s moduly. Když už víme, že existují mechanismy pro načítání tříd, pojďme si ještě v rychlosti definovat pojem class shadowing.
Class shadowing
Class shadowing je nepříjemný následek způsobu načítání tříd v Javě 8 a v dřívějších verzích.
Mějme dvě knihovny, které z nějakého důvodu mají stejný název balíčku a obsahují také stejnou metodu. Taková situace typicky nastane v případě, kdy naše aplikace využívá mnoho knihoven. Tyto knihovny mají také své závislosti, ovšem jejich závislosti už můžou mít starší verzi. Takže se nakonec stane, že knihovna bude na classpath vícekrát.
V takovém případě je použita třída z knihovny, která je na classpath nalezena jako první. Moduly by měly tomuto chování zabránit, protože musí mít unikátní názvy.
Dokud budou používány knihovny, které nejsou modularizovány, bude vždy existovat riziko shadowingu.
Typy modulů
Moduly v Javě lze rozdělit do čtyř kategorií: systémové, aplikační, automatické a bezejmenné.
Systémové moduly
Mezi systémové moduly patří veškeré moduly z Java SE a zároveň moduly z JDK. Tyto moduly lze používat bez žádného speciálního nastavování a jsou automaticky dostupné.
Aplikační moduly
Všechny ostatní moduly, které my jako programátoři vytvoříme, se řadí mezi aplikační moduly. Každý námi vytvořený modul musí mít v aplikaci, kterou vyvíjíme, unikátní název. Pokud to nezajistíme, aplikaci nepůjde zkompilovat.
Automatické moduly
Knihovny, které ještě nebyly modularizovány, můžou být zařazeny mezi
automatické moduly, pokud se vloží na modulepath. Automatické
moduly dostanou název automaticky. Buď se použije název definovaný v
manifestu pod hlavičkou Automatic-Module-Name
, nebo se název
automaticky odvodí z názvu JAR souboru, ve kterém se
automatický modul nachází.
Automatické moduly automaticky exportují všechny balíčky a povolují přístup přes reflexi. Dále jim je umožněno číst veřejné API ostatních modulů (včetně pojmenovaných).
Bezejmenné moduly
Knihovny, které ještě nebyly modularizovány, budou zařazeny mezi bezejmenné moduly, pokud se vloží na classpath.
Bezejmenné moduly také automaticky exportují všechny balíčky a povolují přístup přes reflexi. Dále můžou číst veřejné API ostatních modulů dostupných na modulepath a všechny knihovny dostupné na classpath.
Na druhou stranu není možné používat bezejmenné moduly v pojmenovaných modulech.
Definice modulu
Moduly se definují ve speciálním souboru nazvaném
module-info.java
. Tento soubor najdeme v src
složce
daného modulu. V případě Mavenu se definice modulu vkládá
do složky src/main/java
.
Jako první je třeba v souboru uvést klíčové slovo module
následované názvem modulu. Modul si můžeme nazvat, jak potřebujeme. V
případě Javy zvolili vývojáři jednoduché pojmenování
java.nazev_modulu. Tedy java.base
,
java.logging
, java.xml
atd. Knihovna JavaFX používá
podobné pojmenování: javafx.base
, javafx.controls
,
javafx.fxml
. Velké množství knihoven ale používá jako název
modulu název balíčků, ve kterých se knihovna nachází. Například JAXB
knihovna má název modulu jakarta.xml.bind
.
Po názvu modulu následují složené závorky. Prázdný modul, který na ničem nezávisí a nic neexportuje, může vypadat například takto:
module cz.itnetwork.moduly.prvnimodul { // zde se budou definovat veřejně dostupné moduly // a závislosti }
Dále je vhodné dodržet jmenné konvence projektu, modulů a výsledných
artefaktů. Mějme projekt nazvaný cz.itnetwork.moduly
, který je
rozdělen na několik samostatných modulů prvnimodul
a
druhymodul
. Takový projekt může mít například následující
strukturu:
cz.itnetwork.moduly - kořenová složka celého projektu |- cz.itnetwork.moduly.prvnimodul - první modul | |-src | |- module-info.java - module info prvního modulu | |- cz - balíčky prvního modulu | |- itnetwork | |- moduly | |- prvnimodul | |- TridaA.java - třída v prvním modulu |- cz.itnetwork.moduly.druhymodul - druhý modul |-src |- module-info.java - module info druhého modulu |- cz - balíčky druhého modulu |- itnetwork |- moduly |- druhymodul |- TridaB.java - třída ve druhém modulu
Artefakty můžeme pojmenovat jen po modulu prvnimodul
,
případně mohou obsahovat i celý název
cz.itnetwork.moduly.prvnimodul
. Vše si ukážeme na praktických
příkladech, které nás čekají v dalších lekcích.
V další lekci, Vytvoření prvního modulu v Javě, si po vzoru prvních lekcí OOP vytvoříme projekt "Zdravic" se třemi moduly a ukážeme si, jak jednotlivé moduly konfigurovat, aby byly schopné mezi sebou komunikovat.