Cos'è JPMS? Presentazione del sistema Java Platform Module

Fino a Java 9, l'elemento di organizzazione del codice di primo livello di Java era stato il pacchetto. A partire da Java 9 che è cambiato: sopra il pacchetto ora c'è il modulo. Il modulo raccoglie i pacchetti correlati insieme.

Il Java Platform Module System (JPMS) è una struttura a livello di codice, quindi non cambia il fatto che impacchettiamo Java in file JAR. Alla fine, tutto è ancora raggruppato in file JAR. Il sistema del modulo aggiunge un nuovo descrittore di livello superiore che i JAR possono utilizzare, incorporando il module-info.javafile.

Le app e le organizzazioni su larga scala trarranno vantaggio dai moduli per organizzare meglio il codice. Ma tutti consumeranno moduli, poiché il JDK e le sue classi sono ora modularizzati.

Perché Java ha bisogno di moduli

JPMS è il risultato del progetto Jigsaw, che è stato intrapreso con i seguenti obiettivi dichiarati: 

  • Rendi più facile per gli sviluppatori organizzare app e librerie di grandi dimensioni
  • Migliora la struttura e la sicurezza della piattaforma e di JDK stesso
  • Migliora le prestazioni dell'app
  • Gestire meglio la decomposizione della piattaforma per dispositivi più piccoli

Vale la pena notare che JPMS è una funzionalità SE (Standard Edition), e quindi influenza ogni aspetto di Java da zero. Detto questo, la modifica è progettata per consentire alla maggior parte del codice di funzionare senza modifiche quando si passa da Java 8 a Java 9. Ci sono alcune eccezioni a questo, e le noteremo più avanti in questa panoramica.

L'idea principale alla base di un modulo è quella di consentire la raccolta dei pacchetti correlati che sono visibili al modulo, nascondendo gli elementi ai consumatori esterni del modulo. In altre parole, un modulo consente un altro livello di incapsulamento.

Percorso classe e percorso modulo

In Java fino ad ora il percorso classi è stato la linea di fondo per ciò che è disponibile per un'applicazione in esecuzione. Sebbene il percorso di classe serva a questo scopo ed è ben compreso, finisce per essere un grande secchio indifferenziato in cui sono collocate tutte le dipendenze.

Il percorso del modulo aggiunge un livello sopra il percorso della classe. Funge da contenitore per i pacchetti e determina quali pacchetti sono disponibili per l'applicazione.

Moduli nel JDK

Lo stesso JDK ora è composto da moduli. Cominciamo guardando i dadi e i bulloni di JPMS lì.

Se hai un JDK sul tuo sistema, hai anche il sorgente. Se non hai familiarità con il JDK e come ottenerlo, dai un'occhiata a questo articolo.

All'interno della directory di installazione di JDK c'è una /libdirectory. All'interno di quella directory c'è un src.zipfile. Decomprimilo in una /srcdirectory.

Guarda all'interno della /srcdirectory e vai alla /java.basedirectory. Lì troverai il module-info.javafile. Aprilo.

Dopo i commenti Javadoc in testa, troverai una sezione denominata  module java.base seguita da una serie di exportsrighe. Non ci soffermeremo qui sul formato, poiché diventa abbastanza esoterico. I dettagli possono essere trovati qui.

Puoi vedere che molti dei pacchetti familiari da Java, come java.io, vengono esportati dal java.basemodulo. Questa è l'essenza di un modulo che raccoglie i pacchetti.

Il rovescio della medaglia  exportsè l' requiresistruzione. Ciò consente a un modulo di essere richiesto dal modulo in fase di definizione.

Quando si esegue il compilatore Java sui moduli, si specifica il percorso del modulo in modo simile al percorso della classe. Ciò consente di risolvere le dipendenze.

Creazione di un progetto Java modulare

Diamo un'occhiata a come è strutturato un progetto Java modulizzato.

Creeremo un piccolo programma che ha due moduli, uno che fornisce una dipendenza e l'altro che usa quella dipendenza ed esporta una classe principale eseguibile.

Crea una nuova directory da qualche parte comoda nel tuo file system. Chiamalo /com.javaworld.mod1. Per convenzione, i moduli Java risiedono in una directory che ha lo stesso nome del modulo.

Ora, all'interno di questa directory, crea un module-info.javafile. All'interno, aggiungi il contenuto dal listato 1.

Listato 1: com.javaworld.mod1 / module-info.java

module com.javaworld.mod1 { exports com.javaworld.package1; }

Notare che il modulo e il pacchetto che esporta hanno nomi diversi. Stiamo definendo un modulo che esporta un pacchetto.

Ora creare un file su questa strada, all'interno della directory che contiene il module-info.javafile di: /com.javaworld.mod1/com/javaworld/package1. Assegna un nome al file  Name.java. Metti il ​​contenuto del listato 2 al suo interno.

Listato 2: Name.java

 package com.javaworld.package1; public class Name { public String getIt() { return "Java World"; } } 

Il listato 2 diventerà una classe, un pacchetto e un modulo da cui dipendiamo.

Ora creiamo un'altra directory parallela a /com.javaworld.mod1 e chiamiamola /com.javaworld.mod2. In questa directory, creiamo una module-info.javadefinizione di modulo che importi il ​​modulo che abbiamo già creato, come nel Listato 3.

Listato 3: com.javaworld.mod2 / module-info.java

 module com.javaworld.mod2 { requires com.javaworld.mod1; } 

Il listato 3 è abbastanza autoesplicativo. Definisce il com.javaworld.mod2modulo e richiede com.javaworld.mod1.

All'interno della /com.javaworld.mod2directory, creare un percorso di classe in questo modo: /com.javaworld.mod2/com/javaworld/package2.

Ora aggiungi un file all'interno di chiamato Hello.java, con il codice fornito nel Listato 4.

Listato 4: Hello.java

 package com.javaworld.package2; import com.javaworld.package1.Name; public class Hello { public static void main(String[] args) { Name name = new Name(); System.out.println("Hello " + name.getIt()); } } 

Nel Listato 4, iniziamo definendo il pacchetto, quindi importando la com.javawolrd.package1.Nameclasse. Prendi nota che questi elementi funzionano esattamente come hanno sempre fatto. I moduli hanno cambiato il modo in cui i pacchetti sono resi disponibili a livello di struttura dei file, non a livello di codice.

Similarly, the code itself should be familiar to you. It simply creates a class and calls a method on it to create a classic “hello world” example.

Running the modular Java example

The first step is to create directories to receive the output of the compiler. Create a directory called /target at the root of the project. Inside, create a directory for each module: /target/com.javaworld.mod1 and /target/com.javaworld.mod2.

Step 2 is to compile the dependency module, outputting it to the /target directory. At the root of the project, enter the command in Listing 5. (This assumes the JDK is installed.)

Listing 5: Building Module 1

 javac -d target/com.javaworld.mod1 com.javaworld.mod1/module-info.java com.javaworld.mod1/com/javaworld/package1/Name.java 

This will cause the source to be built along with its module information.

Step 3 is to generate the dependent module. Enter the command shown in Listing 6.

Listing 6: Building Module 2

 javac --module-path target -d target/com.javaworld.mod2 com.javaworld.mod2/module-info.java com.javaworld.mod2/com/javaworld/package2/Hello.java 

Let’s take a look at Listing 6 in detail. It introduces the module-path argument to javac. This allows us to define the module path in similar fashion to the --class-path switch. In this example, we are passing in the target directory, because that is where Listing 5 outputs Module 1.

Next, Listing 6 defines (via the -d switch) the output directory for Module 2. Finally, the actual subjects of compilation are given, as the module-info.java file and class contained in Module 2.

To run, use the command shown in Listing 7.

Listing 7: Executing the module main class

 java --module-path target -m com.javaworld.mod2/com.javaworld.package2.Hello 

The --module-path switch tells Java to use /target directory as the module root, i.e., where to search for the modules. The -m switch is where we tell Java what our main class is. Notice that we preface the fully qualified class name with its module.

You will be greeted with the output Hello Java World.

Backward compatibility 

You may well be wondering how you can run Java programs written in pre-module versions in the post Java 9 world, given that the previous codebase knows nothing of the module path. The answer is that Java 9 is designed to be backwards compatible. However, the new module system is such a big change that you may run into issues, especially in large codebases.

When running a pre-9 codebase against Java 9, you may run into two kinds of errors: those that stem from your codebase, and those that stem from your dependencies.

For errors that stem from your codebase, the following command can be helpful: jdeps. This command when pointed at a class or directory will scan for what dependencies are there, and what modules those dependencies rely on.

For errors that stem from your dependencies, you can hope that the package you are depending on will have an updated Java 9 compatible build. If not you may have to search for alternatives.

One common error is this one:

How to resolve java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException

This is Java complaining that a class is not found, because it has migrated to a module without visibility to the consuming code. There are a couple of solutions of varying complexity and permanency, described here.

Ancora una volta, se scopri tali errori con una dipendenza, controlla con il progetto. Potrebbero avere una build Java 9 da utilizzare.

JPMS è un cambiamento abbastanza radicale e ci vorrà del tempo per adottarlo. Fortunatamente, non c'è fretta urgente, poiché Java 8 è una versione di supporto a lungo termine.

Detto questo, a lungo termine, i progetti più vecchi dovranno migrare e quelli nuovi dovranno utilizzare i moduli in modo intelligente, si spera sfruttando alcuni dei vantaggi promessi.

Questa storia, "Che cos'è JPMS? Presentazione del Java Platform Module System" è stata originariamente pubblicata da JavaWorld.