Inleiding tot Project Jigsaw

1. Inleiding

Project Jigsaw is een overkoepelend project met de nieuwe features gericht op twee aspecten:

  • de introductie van het modulesysteem in de Java-taal
  • en de implementatie ervan in JDK-broncode en Java-runtime

In dit artikel laten we je kennismaken met het Jigsaw-project en de functies ervan en sluiten we het af met een eenvoudige modulaire applicatie.

2. Modulariteit

Simpel gezegd is modulariteit een ontwerpprincipe dat ons helpt bij het bereiken van:

  • losse koppeling tussen componenten
  • duidelijke contracten en afhankelijkheden tussen componenten
  • verborgen implementatie met behulp van sterke inkapseling

2.1. Eenheid van modulariteit

Nu rijst de vraag: wat is de eenheid van modulariteit? In de Java-wereld, vooral met OSGi, werden JAR's beschouwd als de eenheid van modulariteit.

JAR's hebben geholpen bij het groeperen van de gerelateerde componenten, maar ze hebben wel enkele beperkingen:

  • expliciete contracten en afhankelijkheden tussen JAR's
  • zwakke inkapseling van elementen in de JAR's

2.2. JAR Hell

Er was nog een probleem met JAR's - de JAR-hel. Meerdere versies van de JAR's die op het klassenpad lagen, resulteerden in de ClassLoader het laden van de eerst gevonden klasse uit de JAR, met zeer onverwachte resultaten.

Het andere probleem met de JVM die classpath gebruikt, was dat de compilatie van de applicatie succesvol zou zijn, maar de applicatie zal mislukken tijdens runtime met de ClassNotFoundException, vanwege de ontbrekende JAR's op het klassenpad tijdens runtime.

2.3. Nieuwe eenheid van modulariteit

Met al deze beperkingen kwamen de makers van de Java-taal bij het gebruik van JAR als de eenheid van modulariteit met een nieuw construct in de taal genaamd modules. En daarmee is er een heel nieuw modulair systeem gepland voor Java.

3. Project Jigsaw

De belangrijkste motivaties voor dit project zijn:

  • maak een modulesysteem voor de taal - uitgevoerd onder GEP 261
  • pas het toe op de JDK-bron - uitgevoerd onder GEP 201
  • modulariseer de JDKbibliotheken - uitgevoerd onder JEP 200
  • update de runtime om modulariteit te ondersteunen - uitgevoerd onder JEP 220
  • in staat zijn om kleinere runtime te maken met een subset van modules van JDK - uitgevoerd onder JEP 282

Een ander belangrijk initiatief is om de interne API's in de JDK in te kapselen, degenen die onder de zon.* pakketten en andere niet-standaard API's. Deze API's waren nooit bedoeld om door het publiek te worden gebruikt en waren nooit gepland om te worden onderhouden. Maar de kracht van deze API's zorgde ervoor dat de Java-ontwikkelaars ze gebruikten bij de ontwikkeling van verschillende bibliotheken, frameworks en tools. Er zijn vervangingen voorzien voor enkele interne API's en de andere zijn verplaatst naar interne modules.

4. Nieuwe tools voor modulariteit

  • jdeps - helpt bij het analyseren van de codebasis om de afhankelijkheden van JDK API's en de JAR's van derden te identificeren. Het vermeldt ook de naam van de module waar de JDK API te vinden is. Dit maakt het eenvoudiger om de codebasis te modulariseren
  • jdeprscan - helpt bij het analyseren van de codebasis voor het gebruik van verouderde API's
  • jlink - helpt bij het creëren van een kleinere runtime door de applicatie- en de JDK-modules te combineren
  • jmod - helpt bij het werken met jmod-bestanden. jmod is een nieuw formaat voor het verpakken van de modules. Met deze indeling kunnen native code, configuratiebestanden en andere gegevens worden opgenomen die niet in JAR-bestanden passen

5. Module Systeemarchitectuur

Het modulesysteem, geïmplementeerd in de taal, ondersteunt deze als een constructie op het hoogste niveau, net als pakketten. Ontwikkelaars kunnen hun code in modules ordenen en onderlinge afhankelijkheden aangeven in hun respectievelijke moduledefinitiebestanden.

Een moduledefinitiebestand, genaamd module-info.java, bevat:

  • Zijn naam
  • de pakketten die het publiekelijk beschikbaar stelt
  • de modules waarvan het afhankelijk is
  • alle diensten die het verbruikt
  • elke implementatie voor de service die het biedt

De laatste twee items in de bovenstaande lijst worden niet vaak gebruikt. Ze worden alleen gebruikt wanneer services worden geleverd en verbruikt via het java.util.ServiceLoader koppel.

Een algemene structuur van de module ziet er als volgt uit:

src | ---- com.baeldung.reader | | ---- module-info.java | | ---- com | | ---- baeldung | | ---- lezer | | ---- Test.java | ---- com.baeldung.writer | ---- module-info.java | ---- com | ---- baeldung | ---- schrijver | --- -AnotherTest.java

De bovenstaande illustratie definieert twee modules: com.baeldung.reader en com.baeldung.writer. Elk van hen heeft zijn definitie gespecificeerd in module-info.java en de codebestanden die onder com / baeldung / reader en com / baeldung / schrijver, respectievelijk.

5.1. Module Definitie Terminologieën

Laten we eens kijken naar enkele van de terminologieën; we zullen gebruiken bij het definiëren van de module (d.w.z. binnen het module-info.java):

  • module: het moduledefinitiebestand begint met dit trefwoord gevolgd door de naam en de definitie
  • vereist: wordt gebruikt om de modules aan te geven waarvan het afhankelijk is; na dit sleutelwoord moet een modulenaam worden gespecificeerd
  • transitief: wordt gespecificeerd na de vereist trefwoord; dit betekent dat elke module die afhankelijk is van de module die het definieert vereist transitief krijgt een impliciete afhankelijkheid van de <modelnaam>
  • export: wordt gebruikt om de pakketten binnen de module openbaar beschikbaar te maken; na dit sleutelwoord moet een pakketnaam worden gespecificeerd
  • opent: wordt gebruikt om de pakketten aan te geven die alleen tijdens runtime toegankelijk zijn en ook beschikbaar voor introspectie via Reflection API's; dit is nogal significant voor bibliotheken zoals Spring en Hibernate, die sterk afhankelijk zijn van Reflection API's; opent kan ook op moduleniveau worden gebruikt, in welk geval de volledige module tijdens runtime toegankelijk is
  • toepassingen: wordt gebruikt om de service-interface aan te geven die deze module gebruikt; een typenaam, d.w.z. volledige klasse / interfacenaam, moet achter dit sleutelwoord worden gespecificeerd
  • biedt ... met ...: ze worden gebruikt om aan te geven dat het implementaties biedt, geïdentificeerd na de met trefwoord, voor de service-interface die is geïdentificeerd na de biedt trefwoord

6. Eenvoudige modulaire toepassing

Laten we een eenvoudige modulaire applicatie maken met modules en hun afhankelijkheden, zoals aangegeven in het onderstaande diagram:

De com.baeldung.student.model is de root-module. Het definieert de modelklasse com.baeldung.student.model.Student, die de volgende eigenschappen bevat:

openbare klasse Student {private String registrationId; // andere relevante velden, getters en setters}

Het biedt andere modules met typen die zijn gedefinieerd in het com.baeldung.student.model pakket. Dit wordt bereikt door het in het bestand te definiëren module-info.java:

module com.baeldung.student.model {exporteert com.baeldung.student.model; }

De com.baeldung.student.service module biedt een interface com.baeldung.student.service.StudentService met abstracte CRUD-bewerkingen:

openbare interface StudentService {openbare tekenreeks maken (student student); openbare Student lezen (String registrationId); openbare Student-update (Student-student); public String delete (String registrationId); }

Het hangt af van de com.baeldung.student.model module en maakt de typen gedefinieerd in het pakket com.baeldung.student.service beschikbaar voor andere modules:

module com.baeldung.student.service {vereist transitief com.baeldung.student.model; exporteert com.baeldung.student.service; }

We bieden een andere module com.baeldung.student.service.dbimpl, die de implementatie verzorgt com.baeldung.student.service.dbimpl.StudentDbService voor de bovenstaande module:

public class StudentDbService implementeert StudentService {public String create (Student student) {// Creëren student in DB return student.getRegistrationId (); } public Student read (String registrationId) {// Student lezen uit DB return new Student (); } openbare leerlingupdate (leerling-leerling) {// Bijwerken leerling in DB terugkeerleerling; } public String delete (String registrationId) {// Student verwijderen in DB return registrationId; }}

Het hangt er rechtstreeks van af com.baeldung.student.service en transitief op com.baeldung.student.model en de definitie zal zijn:

module com.baeldung.student.service.dbimpl {vereist transitieve com.baeldung.student.service; vereist java.logging; exporteert com.baeldung.student.service.dbimpl; }

De laatste module is een clientmodule - die gebruikmaakt van de service-implementatiemodule com.baeldung.student.service.dbimpl om zijn bewerkingen uit te voeren:

openbare klas StudentClient {openbare statische leegte hoofd (String [] args) {StudentService-service = nieuwe StudentDbService (); service.create (nieuwe student ()); service.read ("17SS0001"); service.update (nieuwe student ()); service.delete ("17SS0001"); }}

En de definitie is:

module com.baeldung.student.client {vereist com.baeldung.student.service.dbimpl; }

7. Compileren en uitvoeren van het monster

We hebben scripts geleverd voor het compileren en uitvoeren van de bovenstaande modules voor de Windows- en Unix-platforms. Deze zijn te vinden onder de core-java-9 project hier. De volgorde van uitvoering voor het Windows-platform is:

  1. compileer-student-model
  2. compileer-student-service
  3. compileer-student-service-dbimpl
  4. compileer-student-client
  5. run-student-client

De volgorde van uitvoering voor het Linux-platform is vrij eenvoudig:

  1. compileer-modules
  2. run-student-client

In de bovenstaande scripts maakt u kennis met de volgende twee opdrachtregelargumenten:

  • –Module-source-path
  • –Module-pad

Java 9 schaft het concept van klassenpad af en introduceert in plaats daarvan modulepad. Dit pad is de locatie waar de modules kunnen worden ontdekt.

We kunnen dit instellen met behulp van het opdrachtregelargument: –Module-pad.

Om meerdere modules tegelijk te compileren maken we gebruik van de –Module-source-path. Dit argument wordt gebruikt om de locatie voor de broncode van de module op te geven.

8. Modulesysteem toegepast op JDK-bron

Elke JDK-installatie wordt geleverd met een src.zip. Dit archief bevat de codebasis voor de JDK Java API's. Als u het archief uitpakt, vindt u meerdere mappen, waarvan er enkele beginnen met Java, weinigen met javafx en de rest met jdk. Elke map vertegenwoordigt een module.

De modules beginnen met Java zijn de JDK-modules, die beginnen met javafx zijn de JavaFX-modules en andere die beginnen met jdk zijn de JDK-tools-modules.

Alle JDK-modules en alle door de gebruiker gedefinieerde modules zijn impliciet afhankelijk van het java.base module. De java.base module bevat veelgebruikte JDK API's zoals onder andere Utils, Collections, IO, Concurrency. De afhankelijkheidsgrafiek van de JDK-modules is:

U kunt ook de definities van de JDK-modules bekijken om een ​​idee te krijgen van de syntaxis voor het definiëren ervan in de module-info.java.

9. Conclusie

In dit artikel hebben we gekeken naar het maken, compileren en uitvoeren van een eenvoudige modulaire applicatie. We zagen ook hoe de JDK-broncode was gemodulariseerd.

Er zijn enkele meer opwindende functies, zoals het maken van kleinere runtime met behulp van de linker-tool - jlink en het maken van modulaire potten, naast andere functies. We zullen u in toekomstige artikelen in detail kennis laten maken met deze functies.

Project Jigsaw is een enorme verandering en we zullen moeten afwachten hoe het wordt geaccepteerd door het ecosysteem van ontwikkelaars, met name door de tools en makers van bibliotheken.

De code die in dit artikel wordt gebruikt, is te vinden op GitHub.