En guide til Java 9-modularitet

1. Oversigt

Java 9 introducerer et nyt abstraktionsniveau over pakker, formelt kendt som Java Platform Module System (JPMS) eller kort sagt "Modules".

I denne vejledning gennemgår vi det nye system og diskuterer dets forskellige aspekter.

Vi bygger også et simpelt projekt for at demonstrere alle de begreber, vi lærer i denne vejledning.

2. Hvad er et modul?

Først og fremmest skal vi forstå, hvad et modul er, før vi kan forstå, hvordan vi bruger dem.

Et modul er en gruppe af nært beslægtede pakker og ressourcer sammen med en ny modulbeskrivelsesfil.

Med andre ord er det en "pakke med Java-pakker" -abstraktion, der giver os mulighed for at gøre vores kode endnu mere genanvendelig.

2.1. Pakker

Pakkerne inde i et modul er identiske med de Java-pakker, vi har brugt siden starten af ​​Java.

Når vi opretter et modul, vi organiserer koden internt i pakker ligesom vi tidligere gjorde med ethvert andet projekt.

Bortset fra at organisere vores kode, bruges pakker til at bestemme, hvilken kode der er offentligt tilgængelig uden for modulet. Vi bruger mere tid på at tale om dette senere i artiklen.

2.2. Ressourcer

Hvert modul er ansvarlig for dets ressourcer, f.eks. Medier eller konfigurationsfiler.

Tidligere havde vi lagt alle ressourcer i rodniveauet i vores projekt og manuelt styret, hvilke ressourcer der tilhørte forskellige dele af applikationen.

Med moduler kan vi sende påkrævede billeder og XML-filer med det modul, der har brug for det, hvilket gør vores projekter meget lettere at administrere.

2.3. Modulbeskrivelse

Når vi opretter et modul, inkluderer vi en deskriptorfil, der definerer flere aspekter af vores nye modul:

  • Navn - navnet på vores modul
  • Afhængigheder - en liste over andre moduler, som dette modul afhænger af
  • Offentlige pakker - en liste over alle pakker, vi vil have adgang til uden for modulet
  • Tilbudte tjenester - vi kan levere serviceimplementeringer, der kan forbruges af andre moduler
  • Tjenester forbrugt - tillader det aktuelle modul at være forbruger af en tjeneste
  • Reflektionstilladelser - tillader eksplicit andre klasser at bruge refleksion for at få adgang til de private medlemmer af en pakke

Reglerne for navngivning af moduler svarer til, hvordan vi navngiver pakker (prikker er tilladt, bindestreger er ikke). Det er meget almindeligt at lave enten projektstil (my.module) eller Reverse-DNS (com.baeldung.mymodule) stilnavne. Vi bruger projektstil i denne vejledning.

Vi har brug for at liste alle pakker, vi vil være offentlige, for som standard er alle pakker modul private.

Det samme gælder for refleksion. Som standard kan vi ikke bruge refleksion over klasser, vi importerer fra et andet modul.

Senere i artiklen ser vi på eksempler på, hvordan du bruger modulbeskrivelsesfilen.

2.4. Modultyper

Der er fire typer moduler i det nye modulsystem:

  • Systemmoduler- Dette er de moduler, der er anført, når vi kører liste-moduler kommandoen ovenfor. De inkluderer Java SE og JDK modulerne.
  • Applikationsmoduler - Disse moduler er det, vi normalt vil bygge, når vi beslutter at bruge moduler. De er navngivet og defineret i den kompilerede modul-info.klasse fil inkluderet i den samlede JAR.
  • Automatiske moduler - Vi kan inkludere uofficielle moduler ved at tilføje eksisterende JAR-filer til modulstien. Navnet på modulet stammer fra navnet på JAR. Automatiske moduler har fuld læseadgang til alle andre moduler, der er indlæst af stien.
  • Unavngivet modul - Når en klasse eller JAR indlæses på klassestien, men ikke modulstien, føjes den automatisk til det ikke-navngivne modul. Det er et catch-all-modul for at opretholde bagudkompatibilitet med tidligere skrevet Java-kode.

2.5. Fordeling

Moduler kan distribueres på to måder: som en JAR-fil eller som et "eksploderet" kompileret projekt. Dette er selvfølgelig det samme som ethvert andet Java-projekt, så det bør ikke komme som nogen overraskelse.

Vi kan oprette multimodulprojekter bestående af en “hovedapplikation” og flere biblioteksmoduler.

Vi skal dog være forsigtige, fordi vi kun kan have et modul pr. JAR-fil.

Når vi konfigurerer vores build-fil, skal vi sørge for at samle hvert modul i vores projekt som en separat krukke.

3. Standardmoduler

Når vi installerer Java 9, kan vi se, at JDK nu har en ny struktur.

De har taget alle de originale pakker og flyttet dem ind i det nye modulsystem.

Vi kan se, hvad disse moduler er ved at skrive i kommandolinjen:

java - liste-moduler

Disse moduler er opdelt i fire hovedgrupper: java, javafx, jdk, og Oracle.

java moduler er implementeringsklasser for kernespecifikationen for SE-sprog.

javafx moduler er FX UI-bibliotekerne.

Alt, hvad JDK selv har brug for, opbevares i jdk moduler.

Og endelig, alt, hvad der er Oracle-specifikt, er i orakel moduler.

4. Modulerklæringer

For at oprette et modul er vi nødt til at placere en speciel fil i roden til vores navngivne pakker modul-info.java.

Denne fil er kendt som modulbeskrivelsen og indeholder alle de data, der er nødvendige for at opbygge og bruge vores nye modul.

Vi konstruerer modulet med en erklæring, hvis krop enten er tom eller består af moduldirektiver:

modul myModuleName {// alle direktiver er valgfri}

Vi starter modulerklæringen med modul nøgleord, og vi følger det med navnet på modulet.

Modulet fungerer med denne erklæring, men vi har ofte brug for mere information.

Det er her moduldirektiverne kommer ind.

4.1. Kræver

Vores første direktiv er kræver. Dette moduldirektiv giver os mulighed for at erklære modulafhængigheder:

modul my.module {kræver modul.navn; }

Nu, min modul har både en kørselstid og en kompileringstidsafhængighedmodule.name.

Og alle offentlige typer, der eksporteres fra en afhængighed, er tilgængelige i vores modul, når vi bruger dette direktiv.

4.2. Kræver statisk

Nogle gange skriver vi kode, der henviser til et andet modul, men som brugere af vores bibliotek aldrig vil bruge.

For eksempel kan vi skrive en hjælpefunktion, der smukt udskriver vores interne tilstand, når et andet logningsmodul er til stede. Men ikke alle forbrugere af vores bibliotek vil have denne funktionalitet, og de vil ikke medtage et ekstra logbibliotek.

I disse tilfælde vil vi bruge en valgfri afhængighed. Ved hjælp af kræver statisk direktivet skaber vi en afhængighed, der kun er kompileringstid:

modul my.module {kræver statisk modul.navn; }

4.3. Kræver transitiv

Vi arbejder ofte med biblioteker for at gøre vores liv lettere.

Men vi er nødt til at sørge for, at ethvert modul, der bringer vores kode ind, også bringer disse ekstra 'transitive' afhængigheder, ellers fungerer de ikke.

Heldigvis kan vi bruge kræver transitiv direktiv om at tvinge enhver downstream-forbruger også til at læse vores krævede afhængigheder:

modul my.module {kræver transitivt modul.navn; }

Nu, når en udvikler kræver min modul, de behøver ikke også at sige kræver modul.navn for at vores modul stadig fungerer.

4.4. Eksport

Som standard udsætter et modul ikke nogen af ​​dets API for andre moduler. Det her stærk indkapsling var en af ​​de vigtigste motivatorer til oprettelse af modulsystemet i første omgang.

Vores kode er betydeligt mere sikker, men nu er vi nødt til at åbne vores API eksplicit for verden, hvis vi vil have den brugbar.

Vi bruger eksport direktiv om at udsætte alle offentlige medlemmer af den navngivne pakke:

modul my.module {eksporterer com.my.package.name; }

Nu, når nogen gør det kræver min modul, vil de have adgang til de offentlige typer i vores com.my.package.name pakke, men ikke nogen anden pakke.

4.5. Eksport ... Til

Vi kan bruge eksport ... til at åbne vores offentlige klasser for verden.

Men hvad hvis vi ikke ønsker, at hele verden skal have adgang til vores API?

Vi kan begrænse, hvilke moduler der har adgang til vores API'er ved hjælp af eksport ... til direktiv.

Svarende til eksport direktiv, erklærer vi en pakke som eksporteret. Men vi viser også hvilke moduler vi tillader at importere denne pakke som en kræver. Lad os se, hvordan dette ser ud:

modul my.module {eksporter com.my.package.name til com.specific.package; }

4.6. Anvendelser

EN service er en implementering af en bestemt grænseflade eller abstrakt klasse, der kan være forbrugt af andre klasser.

Vi udpeger de tjenester, som vores modul bruger sammen med anvendelser direktiv.

Noter det holdets navn vi brug er enten tjenestens interface eller abstrakte klasse, ikke implementeringsklassen:

modul my.module {bruger class.name; }

Vi skal her bemærke, at der er en forskel mellem a kræver direktivet og anvendelser direktiv.

Vi måske kræve et modul, der leverer en tjeneste, vi vil forbruge, men den service implementerer en grænseflade fra en af ​​dens transitive afhængigheder.

I stedet for at tvinge vores modul til at kræve alle transitive afhængigheder bare i tilfælde, bruger vi anvendelser direktiv for at tilføje den nødvendige grænseflade til modulstien.

4.7. Leverer ... Med

Et modul kan også være et service udbyder som andre moduler kan forbruge.

Den første del af direktivet er giver nøgleord. Her er hvor vi sætter grænsefladen eller det abstrakte klassenavn.

Dernæst har vi med direktiv, hvor vi giver implementeringsklassens navn, som enten redskaber grænsefladen eller strækker sig den abstrakte klasse.

Sådan ser det ud sammen:

modul my.module {giver MyInterface MyInterfaceImpl; }

4.8. Åben

Vi nævnte tidligere, at indkapsling var en drivende motivator for designet af dette modulsystem.

Før Java 9 var det muligt at bruge refleksion til at undersøge alle typer og medlemmer i en pakke, selv den privat dem. Intet var virkelig indkapslet, hvilket kan åbne op for alle mulige problemer for udviklere af bibliotekerne.

Fordi Java 9 håndhæver stærk indkapsling, vi er nu udtrykkeligt nødt til at give andre moduler tilladelse til at reflektere over vores klasser.

Hvis vi vil fortsætte med at tillade fuld refleksion, som ældre versioner af Java gjorde, kan vi simpelthen åben hele modulet op:

åbn modul my.module {}

4.9. Åbner

Hvis vi har brug for at tillade afspejling af private typer, men vi ikke vil have al vores kode eksponeret, vi kan bruge åbner direktiv om udsættelse af specifikke pakker.

Men husk, dette åbner pakken for hele verdenen, så sørg for at det er det, du vil have:

modul my.module {åbner com.my.package; }

4.10. Åbner ... til

Okay, så refleksion er undertiden fantastisk, men vi vil stadig have så meget sikkerhed som vi kan komme fra indkapsling. Vi kan selektivt åbne vores pakker til en forhåndsgodkendt liste over moduler, i dette tilfælde ved hjælp af åbner ... til direktiv:

modul my.module {åbner com.my.package til moduleOne, moduleTwo osv .; }

5. Kommandolinjevalg

Nu er support til Java 9-moduler blevet tilføjet til Maven og Gradle, så du behøver ikke lave en masse manuel opbygning af dine projekter. Det er dog stadig værdifuldt at vide hvordan at bruge modulsystemet fra kommandolinjen.

Vi bruger kommandolinjen til vores fulde eksempel nedenunder for at hjælpe med at størkne, hvordan hele systemet fungerer i vores sind.

  • modul-stiVi bruger –Modul-sti mulighed for at specificere modulstien. Dette er en liste over et eller flere biblioteker, der indeholder dine moduler.
  • tilføjelser - I stedet for at stole på moduldeklarationsfilen kan vi bruge kommandolinjekvivalenten til kræver direktiv; –Add-læser.
  • add-eksportUdskiftning af kommandolinje til eksport direktiv.
  • tilføjelsesåbningerUdskift åben klausul i modulerklæringsfilen.
  • tilføjelsesmodulerTilføjer listen over moduler i standardsættet af moduler
  • liste-modulerUdskriver en liste over alle moduler og deres versionstrenge
  • patch-modul - Tilføj eller tilsidesæt klasser i et modul
  • ulovlig adgang = tilladelse | advarsel | nægt - Enten slappe af stærk indkapsling ved at vise en enkelt global advarsel, viser hver advarsel eller mislykkes med fejl. Standard er tilladelse.

6. Synlighed

Vi skal bruge lidt tid på at tale om synligheden af ​​vores kode.

Mange biblioteker er afhængige af refleksion for at udøve deres magi (JUnit og Spring kommer til at tænke på).

Som standard i Java 9 gør vi det kun har adgang til offentlige klasser, metoder og felter i vores eksporterede pakker. Selvom vi bruger refleksion for at få adgang til ikke-offentlige medlemmer og ringe setAccessible (true), vi har ikke adgang til disse medlemmer.

Vi kan bruge åben, åbnerog åbner ... til muligheder for at give kun runtime adgang til refleksion. Bemærk, dette er kun runtime!

Vi vil ikke være i stand til at kompilere mod private typer, og vi skulle aldrig have brug for det alligevel.

Hvis vi skal have adgang til et modul til refleksion, og vi ikke er ejeren af ​​dette modul (dvs. vi kan ikke bruge åbner ... til direktiv), så er det muligt at bruge kommandolinjen –Add-opens mulighed for at tillade egne modulers refleksionsadgang til det låste modul ved kørselstid.

Den eneste advarsel her er, at du skal have adgang til kommandolinjeargumenterne, der bruges til at køre et modul for at dette skal fungere.

7. At sætte det hele sammen

Nu hvor vi ved, hvad et modul er, og hvordan vi bruger dem, lad os gå videre og bygge et simpelt projekt for at demonstrere alle de koncepter, vi lige har lært.

For at holde tingene enkle bruger vi ikke Maven eller Gradle. I stedet stoler vi på kommandolinjeværktøjerne til at opbygge vores moduler.

7.1. Opsætning af vores projekt

Først skal vi oprette vores projektstruktur. Vi opretter flere mapper til at organisere vores filer.

Start med at oprette projektmappen:

mkdir modul-projekt cd modul-projekt

Dette er basen for hele vores projekt, så tilføj filer her, såsom Maven eller Gradle build-filer, andre kildekataloger og ressourcer.

Vi har også oprettet en mappe, der indeholder alle vores projektspecifikke moduler.

Dernæst opretter vi en modulkatalog:

mkdir simple-moduler

Sådan ser vores projektstruktur ud:

modul-projekt | - // src hvis vi bruger standardpakken | - // build-filer går også på dette niveau | - simple-moduler | - hello.modules | - com | - baeldung | - moduler | - hej | - main .app | - com | - baeldung | - moduler | - main

7.2. Vores første modul

Nu hvor vi har den grundlæggende struktur på plads, lad os tilføje vores første modul.

Under enkle moduler mappe, skal du oprette en ny mappe kaldet hej moduler.

Vi kan navngive dette, hvad vi vil, men følg reglerne for navnepakke (dvs. perioder for at adskille ord osv.). Vi kan endda bruge navnet på vores hovedpakke som modulnavnet, hvis vi vil, men normalt vil vi holde os til det samme navn, som vi ville bruge til at oprette en JAR af dette modul.

Under vores nye modul kan vi oprette de pakker, vi ønsker. I vores tilfælde skal vi oprette en pakkestruktur:

com.baeldung.modules.hello

Opret derefter en ny klasse kaldet HelloModules.java i denne pakke. Vi holder koden enkel:

pakke com.baeldung.modules.hello; offentlig klasse HelloModules {public static void doSomething () {System.out.println ("Hello, Modules!"); }}

Og endelig i hej moduler rodkatalog, tilføj i vores modulbeskrivelse; modul-info.java:

modul hello.modules {eksporterer com.baeldung.modules.hello; }

For at holde dette eksempel simpelt, alt hvad vi laver, er at eksportere alle offentlige medlemmer af com.baeldung.modules.hello pakke.

7.3. Vores andet modul

Vores første modul er fantastisk, men det gør ikke noget.

Vi kan oprette et andet modul, der bruger det nu.

Under vores enkle moduler bibliotek, skal du oprette et andet modulkatalog kaldet main.app. Vi starter med modulbeskrivelsen denne gang:

modul main.app {kræver hello.modules; }

Vi behøver ikke at udsætte noget for omverdenen. I stedet er alt hvad vi skal gøre, at afhænge af vores første modul, så vi har adgang til de offentlige klasser, det eksporterer.

Nu kan vi oprette en applikation, der bruger den.

Opret en ny pakkestruktur: com.baeldung.modules.main.

Opret nu en ny klassefil kaldet MainApp.java.

pakke com.baeldung.modules.main; import com.baeldung.modules.hello.HelloModules; public class MainApp {public static void main (String [] args) {HelloModules.doSomething (); }}

Og det er al den kode, vi har brug for for at demonstrere moduler. Vores næste trin er at opbygge og køre denne kode fra kommandolinjen.

7.4. Opbygning af vores moduler

For at opbygge vores projekt kan vi oprette et simpelt bash-script og placere det i roden af ​​vores projekt.

Opret en fil, der hedder kompilér-simple-modules.sh:

#! / usr / bin / env bash javac -d outDir - modul-source-path simple-modules $ (find simple-modules -name "* .java")

Der er to dele til denne kommando, javac og finde kommandoer.

Det finde kommandoen udsender simpelthen en liste over alle.java filer under vores simple-modul-mappe. Vi kan derefter føre listen direkte ind i Java-kompilatoren.

Det eneste, vi skal gøre anderledes end de ældre versioner af Java, er at levere en modul-kilde-sti parameter for at informere compileren om, at den bygger moduler.

Når vi har kørt denne kommando, har vi en outDir mappe med to kompilerede moduler indeni.

7.5. Kører vores kode

Og nu kan vi endelig køre vores kode for at kontrollere, at moduler fungerer korrekt.

Opret en anden fil i roden af ​​projektet: run-simple-module-app.sh.

#! / usr / bin / env bash java --module-sti udDir -m main.app/com.baeldung.modules.main.MainApp

For at køre et modul skal vi mindst levere modul-sti og hovedklassen. Hvis alt fungerer, skal du se:

> $ ./run-simple-module-app.sh Hej, moduler!

7.6. Tilføjelse af en tjeneste

Nu hvor vi har en grundlæggende forståelse af, hvordan man bygger et modul, lad os gøre det lidt mere kompliceret.

Vi skal se, hvordan vi bruger giver ... med og anvendelser direktiver.

Start med at definere en ny fil i hej moduler modul navngivet HelloInterface.java:

offentlig grænseflade HelloInterface {void sayHello (); }

For at gøre tingene lette skal vi implementere denne grænseflade med vores eksisterende HelloModules.java klasse:

offentlig klasse HelloModules implementerer HelloInterface {public static void doSomething () {System.out.println ("Hello, Modules!"); } offentligt tomrum sayHello () {System.out.println ("Hej!"); }}

Det er alt, hvad vi skal gøre for at skabe en service.

Nu skal vi fortælle verden, at vores modul leverer denne service.

Føj følgende til vores modul-info.java:

giver com.baeldung.modules.hello.HelloInterface med com.baeldung.modules.hello.HelloModules;

Som vi kan se, erklærer vi grænsefladen, og hvilken klasse implementerer den.

Dernæst skal vi forbruge dette service. I vores main.app modul, lad os tilføje følgende til vores modul-info.java:

bruger com.baeldung.modules.hello.HelloInterface;

Endelig kan vi i vores hovedmetode bruge denne service via en ServiceLoader:

Iterable services = ServiceLoader.load (HelloInterface.class); HelloInterface service = services.iterator (). Næste (); service.sayHello ();

Kompilér og kør:

#> ./run-simple-module-app.sh Hej moduler! Hej!

Vi bruger disse direktiver til at være meget mere eksplicit over, hvordan vores kode skal bruges.

Vi kunne placere implementeringen i en privat pakke, mens vi eksponerede grænsefladen i en offentlig pakke.

Dette gør vores kode meget mere sikker med meget lidt ekstra omkostninger.

Gå videre og prøv nogle af de andre direktiver for at lære mere om moduler og hvordan de fungerer.

8. Tilføjelse af moduler til det Unnamed-modul

Det unavngivne modulkoncept svarer til standardpakken. Derfor betragtes det ikke som et ægte modul, men kan ses som standardmodulet.

Hvis en klasse ikke er medlem af et navngivet modul, betragtes det automatisk som en del af dette unavngivne modul.

Nogle gange er vi nødt til at tilføje moduler til standardrodsættet for at sikre specifikke platform-, biblioteks- eller tjenesteudbydermoduler i modulgrafen. For eksempel, når vi forsøger at køre Java 8-programmer som de er med Java 9-compiler, kan det være nødvendigt at tilføje moduler.

Generelt, muligheden for at tilføje de navngivne moduler til standardsættet med rodmoduler er –Add-moduler (,)* hvor er et modulnavn.

For eksempel at give adgang til alle java.xml.bind moduler syntaksen ville være:

--add-modules java.xml.bind

For at bruge dette i Maven kan vi integrere det samme i maven-compiler-plugin:

 org.apache.maven.plugins maven-compiler-plugin 3.8.0 9 9 --add-modules java.xml.bind 

9. Konklusion

I denne omfattende guide fokuserede vi på og dækkede det grundlæggende i det nye Java 9-modulsystem.

Vi startede med at tale om, hvad et modul er.

Dernæst talte vi om, hvordan man finder ud af, hvilke moduler der er inkluderet i JDK.

Vi dækkede også modulerklæringsfilen i detaljer.

Vi afrundede teorien ved at tale om de forskellige kommandolinjeargumenter, vi har brug for for at opbygge vores moduler.

Endelig omsatte vi al vores tidligere viden i praksis og skabte en simpel applikation bygget oven på modulsystemet.

For at se denne kode og mere skal du sørge for at tjekke den ud på Github.