Какво е JPMS? Представяне на системата на платформата Java

До Java 9 елементът на организацията на кода от най-високо ниво на Java беше пакетът. Започвайки с Java 9, която се промени: над пакета сега е модулът. Модулът събира свързани пакети заедно.

Java Platform Module System (JPMS) е структура на ниво код, така че не променя факта, че пакетираме Java в JAR файлове. В крайна сметка всичко все още е обединено в JAR файлове. Модулната система добавя нов дескриптор от по-високо ниво, който JAR могат да използват, чрез включване на module-info.javaфайла.

Мащабните приложения и организации ще се възползват от модулите, за да организират по-добре кода. Но всички ще консумират модули, тъй като JDK и неговите класове вече са модулирани.

Защо Java се нуждае от модули

JPMS е резултатът от проект Jigsaw, който е предприет със следните посочени цели: 

  • Улеснете разработчиците да организират големи приложения и библиотеки
  • Подобрете структурата и сигурността на платформата и самия JDK
  • Подобрете работата на приложението
  • По-добро справяне с разлагането на платформата за по-малки устройства

Струва си да се отбележи, че JPMS е функция SE (Standard Edition) и следователно влияе на всеки аспект на Java от самото начало. След като каза това, промяната е проектирана да позволи на повечето кодове да функционират без модификации при преместване от Java 8 на Java 9. Има някои изключения от това и ще ги отбележим по-късно в този преглед.

Основната идея зад модула е да позволи събирането на свързани пакети, които са видими за модула, като същевременно скрива елементи от външни потребители на модула. С други думи, модулът позволява друго ниво на капсулиране.

Клас път срещу модул път

В Java досега пътят на класа е бил най-долният ред за това, което е достъпно за работещо приложение. Въпреки че пътят на класа служи за тази цел и е добре разбран, той в крайна сметка е голяма, недиференцирана кофа, в която са поставени всички зависимости.

Пътят на модула добавя ниво над пътя на класа. Той служи като контейнер за пакети и определя кои пакети са достъпни за приложението.

Модули в JDK

Самият JDK сега е съставен от модули. Нека започнем с разглеждането на гайките на JPMS там.

Ако имате JDK в системата си, имате и източника. Ако не сте запознати с JDK и как да го получите, разгледайте тази статия.

Във вашата директория за инсталиране на JDK има /libдиректория. Вътре в тази директория има src.zipфайл. Разархивирайте го в /srcдиректория.

Погледнете в /srcдиректорията и отидете до нея /java.base. Там ще намерите module-info.javaфайла. Отвори го.

След коментарите на Javadoc в главата, ще намерите раздел с име,  module java.base последван от поредица от exportsредове. Няма да се спираме на формата тук, тъй като той става доста езотеричен. Подробностите можете да намерите тук.

Можете да видите, че много от познатите пакети от Java, като например java.io, се експортират от java.baseмодула. Това е същността на модула, събиращ пакетите.

Обратната страна на  exportsе requiresинструкцията. Това позволява модул да се изисква от модула, който се дефинира.

Когато стартирате Java компилатора срещу модули, вие указвате пътя на модула по начин, подобен на пътя на класа. Това позволява разрешаването на депеденциите.

Създаване на модулен Java проект

Нека да разгледаме как е структуриран модулиран Java проект.

Ще направим малка програма, която има два модула, един, който доставя зависимост, а другият, който използва тази зависимост и експортира изпълним основен клас.

Създайте нова директория някъде удобно във вашата файлова система. Обадете се /com.javaworld.mod1. По споразумение модулите Java живеят в директория, която има същото име като модула.

Сега, в тази директория, създайте module-info.javaфайл. Вътре добавете съдържанието от Листинг 1.

Листинг 1: com.javaworld.mod1 / module-info.java

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

Забележете, че модулът и пакетът, който той експортира, са различни имена. Определяме модул, който експортира пакет.

Сега създадете файл по този път, вътре в директорията, която съдържа module-info.javaфайла: /com.javaworld.mod1/com/javaworld/package1. Наименувайте файла  Name.java. Поставете съдържанието на Листинг 2 вътре в него.

Листинг 2: Name.java

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

Списък 2 ще се превърне в клас, пакет и модул, от които зависим.

Сега нека създадем друга директория, успоредна на /com.javaworld.mod1 и да я извикаме /com.javaworld.mod2. В тази директория, нека създадем module-info.javaдефиниция на модул, която импортира модула, който вече сме създали, както е в Листинг 3.

Листинг 3: com.javaworld.mod2 / module-info.java

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

Листинг 3 е доста обяснителен. Той определя com.javaworld.mod2модула и изисква com.javaworld.mod1.

Вътре в /com.javaworld.mod2директорията създайте път на класа по следния начин /com.javaworld.mod2/com/javaworld/package2:.

Сега добавете файл вътре, наречен Hello.java, с кода, предоставен в листинг 4.

Листинг 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()); } } 

В Листинг 4 започваме с дефиниране на пакета, след което импортираме com.javawolrd.package1.Nameкласа. Обърнете внимание, че тези елементи функционират както винаги. Модулите са променили начина, по който пакетите се предоставят на ниво файлова структура, а не на ниво код.

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.

Отново, ако откриете такива грешки при зависимост, консултирайте се с проекта. Те могат да имат Java 9 компилация, която можете да използвате.

JPMS е доста обширна промяна и ще отнеме време да се приеме. За щастие няма спешно бързане, тъй като Java 8 е дългосрочна версия за поддръжка.

Като се има предвид това, в дългосрочен план по-старите проекти ще трябва да мигрират, а новите ще трябва да използват интелигентно модули, надявайки се да се възползват от някои от обещаните ползи.

Тази история, „Какво е JPMS? Представяне на системата на платформата Java Java“ първоначално е публикувана от JavaWorld.