Java 9 модули: Модульность в Java 9 / Хабр

Содержание

Java 9. Project Jigsaw. Модульность

Ключевой особенностью предстоящего релиза Java 9 является поддержка модульности, которую принесёт Project Jigsaw. Цель этого проекта — сделать Java SE платформу более гибкой, производительной и защищённой за счёт разбиения JDK на модули и внедрения модульной системы.

Модуль
В отличие от обычного jar-файла, который означал для JVM лишь хранилище кода и ресурсов, jar-модуль содержит класс module-info, который предоставляет:
  — имя модуля;
  — информацию о модулях-зависимостях, которые нужны для корректной компиляции и работы;
  — информацию о пакетах, которые открывает (экспортирует) этот модуль;
  — список сервисов, которые поставляет модуль в рантайме.

В третьем пункте кроется одно важное изменение, которого доселе так не хватало. Теперь, если класс объявлен публичным, это ещё не значит, что он будет доступен всем модулям. Область видимости public становится более широкой:
  — public class в экспортируемом пакете — доступен всем модулям, у которых этот модуль в зависимостях;
  — public class в экспортируемом конкретному модулю пакете — доступен только указанному модулю;
  — public class без экспорта пакета — доступен всем классам данного модуля.

Вот пример содержимого module-info.java:

  1. module com.example.samplemodule {

  2.     requires com.example.sampleapp;

  3.     requires public java.httpclient;

  4.     exports com.example.samplemodule.model;

  5.     exports com.example.samplemodule.spi;

  6.     uses com.example.samplemodule.spi.DataProvider;

  7.     provides com.example.sampleapp.spi.SettingsProvider

  8.         with com.example.samplemodule.ModuleSettingsProvider;

  9. }


Данный модуль имеет имя com.example.samplemodule (принято именовать модули по пакетам, чтобы избежать конфликтов). Он зависит от модулей com.example.sampleapp, java.httpclient и java.base (который используется по умолчанию для всех модулей). Причём java.httpclient будет зависимостью для всех модулей, которые используют com.example.samplemodule. Наш модуль экспортирует пакеты com.example.samplemodule.model и com.example.samplemodule.spi, так что все публичные классы в этих пакетах будут доступны другим модулям, которые зависят от него. Модуль использует com.example.samplemodule.spi.DataProvider для получения данных из других модулей. А также он поставляет настройки сервису другого модуля, реализуя интерфейс com.example.sampleapp.spi.SettingsProvider в классе com.example.samplemodule.ModuleSettingsProvider.

А теперь к практике.

Пример 1. Прямая зависимость двух модулей
У нас будет два проекта: TimeApp — главное приложение, которое выводит время, поставляемое вторым проектом — TimeLocalModule.

В этом примере TimeApp в главном классе будет вызывать метод публичного класса второго модуля напрямую. Чтобы это работало, мы должны в главном модуле объявить зависимость от второго модуля, а во втором модуле экспортировать пакет с классом, предоставляющим строку текущего времени. Иначе мы даже не сможем импортировать пакет второго модуля.

TimeApp

  1. // com/example/timeapp/Main.java

  2. package com.example.timeapp;

  3.  

  4. import com.example.timelocal.TimeLocal;

  5.  

  6. public final class Main {

  7.  

  8.     public static void main(String[] args) {

  9.         System.out.format(«Current time: %s%n», TimeLocal.now());

  10.     }

  11. }

  1. // module-info.java

  2. module com.example.timeapp {

  3.     requires java.base;

  4.     requires com.example.timelocalmodule;

  5. }


TimeLocalModule
  1. // com/example/timelocal/TimeLocal.java

  2. package com.example.timelocal;

  3.  

  4. import java.time.LocalDateTime;

  5. import java.time.format.DateTimeFormatter;

  6.  

  7. public class TimeLocal {

  8.  

  9.     public static String now() {

  10.         return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now());

  11.     }

  12. }

  1. // module-info.java

  2. module com.example.timelocalmodule {

  3.     exports com.example.timelocal;

  4. }


В настройках главного проекта нужно добавить зависимость от проекта TimeLocalModule:

Netbeans показывает область видимости пакета, com.example.timelocal теперь публичный:

Для компиляции и запуска из командной строки можно воспользоваться скриптом:

  1. @echo off

  2.  

  3. set JAVA9_HOME=D:\Program Files\Java\jdk-9

  4. set JAVA9=»%JAVA9_HOME%/bin/java»

  5. set JAVAC9=»%JAVA9_HOME%/bin/javac»

  6.  

  7. mkdir mods\com.example.timeapp

  8. mkdir mods\com.example.timelocalmodule

  9.  

  10. echo Compile timelocalmodule

  11. %JAVAC9% -d mods/com.example.timelocalmodule ^

  12.     TimeLocalModule/src/module-info.java TimeLocalModule/src/com/example/timelocal/TimeLocal.java

  13.  

  14. echo Compile timeapp

  15. %JAVAC9% —module-path mods -d mods/com.example.timeapp ^

  16.     TimeApp/src/module-info.java TimeApp/src/com/example/timeapp/Main.java

  17.  

  18. echo Run timeapp

  19. %JAVA9% —module-path mods ^

  20.         -m com.example.timeapp/com.example.timeapp.Main


Либо запускаем в NetBeans IDE и получаем:
  1. Current time: 2016-10-20T18:36:36.6763098


Пример на GitHub

Пример 2. Сервисы и ServiceLoader
Предыдущий пример лишь демонстрирует новый способ подключения библиотеки, теперь же сделаем настоящий модуль. Модифицируем наш пример так, чтобы не главное приложение было зависимо от модулей, а наоборот — модули зависели от главного приложения. На этапе компиляции оно не будет ничего знать о модулях, расширяющих функционал, а вот в рантайме оно будет брать доступные расширения.

Ранее, доступные реализации лежали в текстовом файле в META-INF/services/exampleservice, ServiceLoader читал оттуда названия классов и поставлял в итераторе готовые реализации интерфейсов или абстрактных классов. Теперь же можно назначать реализацию в module-info дополнительных модулей:

  1. module additional {

  2.   provides com.example.spi.Provider // базовый интерфейс

  3.       with com.impl.ProviderImpl; // реализация в классе этого модуля

  4. }

А для использования в основном модуле нужно указать
  1. module main {

  2.   uses com.example.spi.Provider;

  3. }

И обрабатывать полученные реализации:
  1. ServiceLoader<Provider> sl = ServiceLoader.load(Provider.class);

  2. for (Provider p : sl) {

  3.   // ..

  4. }


В отличие от старого подхода, связь теперь проверяется на этапе компиляции.

Создадим отдельный пакет com.example.timeapp.spi, который будет публичным для модулей-зависимостей. В нём будет интерфейс, реализуя который, другие модули будут предоставлять информацию.

TimeApp

  1. // com/example/timeapp/spi/TimeProvider.java

  2. package com.example.timeapp.spi;

  3.  

  4. public interface TimeProvider {

  5.  

  6.     String now();

  7. }

  1. // module-info.java

  2. module com.example.timeapp {

  3.     requires java.base;

  4.  

  5.     exports com.example.timeapp.spi;

  6. }


Не забудьте убрать зависимость от модуля в свойствах проекта, иначе получите циклическую зависимость.

TimeLocalModule

  1. // com/example/timelocal/TimeLocalProvider.java

  2. package com.example.timelocal;

  3.  

  4. import com.example.timeapp.spi.TimeProvider;

  5. import java.time.LocalDateTime;

  6. import java.time.format.DateTimeFormatter;

  7.  

  8. public class TimeLocalProvider implements TimeProvider {

  9.  

  10.     @Override

  11.     public String now() {

  12.         return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now());

  13.     }

  14. }

  1. // module-info.java

  2. module com.example.timelocalmodule {

  3.     requires com.example.timeapp;

  4.  

  5.     provides com.example.timeapp.spi.TimeProvider

  6.         with com.example.timelocal.TimeLocalProvider;

  7. }


Осталось только указать главному приложению, чтобы он загружал модули, которые в рантайме поставляют реализацию интерфейса TimeProvider. Для этого есть специальная система загрузки сервисов — класс ServiceLoader. Передав ему класс интерфейса провайдера и зарегистрировав этот класс в module-info ключевым словом uses, мы можем получить список реализаций, который доступны в рантайме.

TimeApp

  1. // com/example/timeapp/Main.java

  2. package com.example.timeapp;

  3.  

  4. import com.example.timeapp.spi.TimeProvider;

  5. import java.util.ServiceLoader;

  6.  

  7. public final class Main {

  8.  

  9.     public static void main(String[] args) {

  10.         ServiceLoader<TimeProvider> serviceLoader = ServiceLoader.load(TimeProvider.class);

  11.         serviceLoader.forEach(t -> {

  12.             System.out.format(«Current time: %s%n», t.now());

  13.             System.out.println(t.getClass());

  14.         });

  15.     }

  16. }


Если сейчас запустить приложение, то мы получим ошибку:
  1. Exception in thread «main» java.util.ServiceConfigurationError: com.example.timeapp.spi.TimeProvider: use not declared in module com.example.timeapp

  2.     at java.util.ServiceLoader.fail([email protected]/ServiceLoader.java:386)

  3.     at java.util.ServiceLoader.checkModule([email protected]/ServiceLoader.java:371)

  4.     at java.util.ServiceLoader.<init>([email protected]/ServiceLoader.java:319)

  5.     at java.util.ServiceLoader.<init>([email protected]/ServiceLoader.java:351)

  6.     at java.util.ServiceLoader.load([email protected]/ServiceLoader.java:1021)

  7.     at com.example.timeapp.Main.main(com.example.timeapp/Main.java:9)

Это потому, что мы не зарегистрировали класс в module-info. Сделаем это:
  1. // module-info.java

  2. module com.example.timeapp {

  3.     requires java.base;

  4.  

  5.     exports com.example.timeapp.spi;

  6.  

  7.     uses com.example.timeapp.spi.TimeProvider;

  8. }


Теперь при запуске не будет ошибки, но и данные не появятся. Всё потому, что модули не добавлены в зависимости.

В NetBeans IDE в свойствах проекта на вкладке Run нужно добавить модуль в Modulepath, однако добавлять не проект, а jar-файл (предварительно нужно скомпилировать модуль — Clean and Build).

Запускаем и получаем вывод:

  1. Current time: 2016-10-20T20:41:39.9614732

  2. class com.example.timelocal.TimeLocalProvider

Можно добавить ещё один модуль, который будет брать информацию из другого источника, например из Интернета.

TimeNetworkModule

  1. // com/example/timenetwork/TimeNetworkProvider.java

  2. package com.example.timenetwork;

  3.  

  4. import com.example.timeapp.spi.TimeProvider;

  5. import java.io.IOException;

  6. import java.net.URI;

  7. import java.net.http.HttpClient;

  8. import java.net.http.HttpResponse;

  9.  

  10. public class TimeNetworkProvider implements TimeProvider {

  11.  

  12.     @Override

  13.     public String now() {

  14.         try {

  15.             return HttpClient.getDefault()

  16.                     .request(URI.create(«http://www.timeapi.org/utc/now»))

  17.                     .header(«User-Agent», «Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36»)

  18.                     .GET()

  19.                     .response()

  20.                     .body(HttpResponse.asString());

  21.         } catch (IOException | InterruptedException ex) {

  22.             throw new RuntimeException(«Network error»);

  23.         }

  24.     }

  25. }

  1. // module-info.java

  2. module com.example.timenetworkmodule {

  3.     requires com.example.timeapp;

  4.     requires java.httpclient;

  5.  

  6.     provides com.example.timeapp.spi.TimeProvider

  7.             with com.example.timenetwork.TimeNetworkProvider;

  8. }


У нового модуля добавлена зависимость java.httpclient для работы с новым HTTP/2 клиентом.

Компилируем и запускаем в NetBeans IDE или из командной строки:

  1. rem compile.cmd

  2. echo Compile timeapp

  3. %JAVAC9% -d mods/com.example.timeapp ^

  4.     TimeApp/src/module-info.java TimeApp/src/com/example/timeapp/Main.java ^

  5.     TimeApp/src/com/example/timeapp/spi/TimeProvider.java

  6.  

  7. echo Compile timelocalmodule

  8. %JAVAC9% —module-path mods -d mods/com.example.timelocalmodule ^

  9.     TimeLocalModule/src/module-info.java TimeLocalModule/src/com/example/timelocal/TimeLocal.java ^

  10.     TimeLocalModule/src/com/example/timelocal/TimeLocalProvider.java

  11.  

  12. echo Compile timenetworkmodule

  13. %JAVAC9% —module-path mods -d mods/com.example.timenetworkmodule ^

  14.     TimeNetworkModule/src/module-info.java ^

  15.     TimeNetworkModule/src/com/example/timenetwork/TimeNetworkProvider.java

  16.  

  17. echo Run timeapp

  18. %JAVA9% —module-path mods ^

  19.         -m com.example.timeapp/com.example.timeapp.Main


Получаем:
  1. Current time: 2016-10-20T20:55:03.3944269

  2. class com.example.timelocal.TimeLocalProvider

  3. Current time: 2016-10-20T17:55:06+00:00

  4. class com.example.timenetwork.TimeNetworkProvider


Пример на GitHub

Пример 3. Модули и ресурсы
Исходя из требований модульной системы, ресурсы модуля должны быть доступны только этому модулю. Значит загружать их мы можем только в модуле, а на дальнейшую передачу другим модулям запретов нет.

Давайте сделаем простую форму, где для каждого модуля будет отдельная кнопка, по нажатию которой будем выводить время.

В главном приложении нужно добавить зависимость от java.desktop, чтобы иметь возможность импортировать пакет java.awt и java.swing.

  1. // module-info.java

  2. module com.example.timeapp {

  3.     requires java.base;

  4.     requires java.desktop;

  5.  

  6.     exports com.example.timeapp.spi;

  7.  

  8.     uses com.example.timeapp.spi.TimeProvider;

  9. }


Изменим и TimeProvider, чтобы иметь возможность получить иконку модуля:
  1. // com/example/timeapp/spi/TimeProvider.java

  2. package com.example.timeapp.spi;

  3.  

  4. import java.awt.Image;

  5.  

  6. public interface TimeProvider {

  7.  

  8.     String now();

  9.  

  10.     Image icon();

  11. }


  1. // com/example/timeapp/Main.java

  2. public final class Main extends JFrame {

  3.  

  4.     public static void main(String[] args) {

  5.         final Main frame = new Main();

  6.         ServiceLoader<TimeProvider> serviceLoader = ServiceLoader.load(TimeProvider.class);

  7.         serviceLoader.forEach(t -> {

  8.             final JButton button = new JButton();

  9.             button.setText(t.getClass().getSimpleName());

  10.             final Image icon = t.icon();

  11.             if (icon != null) {

  12.                 button.setIcon(new ImageIcon(icon));

  13.             }

  14.             button.addActionListener(e -> {

  15.                 frame.outputLabel.setText(String.format(«Current time: %s%n», t.now()));

  16.             });

  17.             frame.modulesPanel.add(button);

  18.         });

  19.         frame.pack();

  20.         frame.setVisible(true);

  21.     }

  22.  

  23.     private final JPanel modulesPanel;

  24.     private final JLabel outputLabel;

  25.  

  26.     public Main() {

  27.         super(«Jigsaw Example»);

  28.  

  29.         modulesPanel = new JPanel();

  30.         modulesPanel.setLayout(new BoxLayout(modulesPanel, BoxLayout.LINE_AXIS));

  31.         add(modulesPanel, BorderLayout.NORTH);

  32.  

  33.         outputLabel = new JLabel(«output»);

  34.         outputLabel.setHorizontalAlignment(SwingConstants.CENTER);

  35.         add(outputLabel, BorderLayout.CENTER);

  36.  

  37.         setDefaultCloseOperation(EXIT_ON_CLOSE);

  38.     }

  39. }


Остальным модулям добавим картинки и реализуем метод Image icon() изменённого TimeProvider. Вот только есть одно но. Модули пока ещё не зависят от java.desktop, поэтому класс Image мы не сможем импортировать пока не добавим зависимость в module-info. Как быть? Для этого в module-info есть специальный синтаксис, который позволит указать, что зависимость должна автоматически распространяться и на другие модули:
  1. requires public java.desktop;


Тогда module-info главного приложения будет выглядеть так:
  1. // module-info.java

  2. module com.example.timeapp {

  3.     requires java.base;

  4.     requires public java.desktop;

  5.  

  6.     exports com.example.timeapp.spi;

  7.  

  8.     uses com.example.timeapp.spi.TimeProvider;

  9. }


Загружаем изображение:
  1. public class TimeLocalProvider implements TimeProvider {

  2.  

  3.     private static Image icon;

  4.  

  5.     @Override

  6.     public String now() {

  7.         return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now());

  8.     }

  9.  

  10.     @Override

  11.     public Image icon() {

  12.         if (icon == null) {

  13.             try (InputStream is = getClass().getResourceAsStream(«/res/icon.png»)) {

  14.                 if (is != null)

  15.                     icon = ImageIO.read(is);

  16.             } catch (IOException ignore) { }

  17.         }

  18.         return icon;

  19.     }

  20. }


Так же и во втором модуле. Запускаем — работает!

Пример на GitHub

Пример 4. Автоматический модуль
Хорошо, а как быть с библиотеками ранних версий Java? Мы не можем добавить им module-info да и желания нет что-то делать. Как быть? У нас есть два выбора:
     1. Добавить библиотеку в modulepath, тогда она становится автоматическим модулем: его именем будет имя jar-файла, экспортировать он будет все свои пакеты, а читать все открытые пакеты других модулей.
     2. Добавить библиотеку в classpath, тогда она становится безымянным модулем: добавлять как зависимость мы его, разумеется, не сможем, а читать он сможет все пакеты модулей.

Для примера создадим обычную библиотеку без module-info, реализуем TimeProvider (не забудьте добавить главный проект в classpath):

  1. // com/example/timemidnight/MidnightProvider.java

  2. package com.example.timemidnight;

  3.  

  4. import com.example.timeapp.spi.TimeProvider;

  5. import java.awt.Image;

  6. import java.io.IOException;

  7. import java.io.InputStream;

  8. import javax.imageio.ImageIO;

  9.  

  10. public class MidnightProvider implements TimeProvider {

  11.  

  12.     private static Image icon;

  13.  

  14.     @Override

  15.     public String now() {

  16.         return «00:00»;

  17.     }

  18.  

  19.     @Override

  20.     public Image icon() {

  21.         if (icon == null) {

  22.             try (InputStream is = getClass().getResourceAsStream(«/res/icon.png»)) {

  23.                 if (is != null)

  24.                     icon = ImageIO.read(is);

  25.             } catch (IOException ignore) { }

  26.         }

  27.         return icon;

  28.     }

  29. }


Скомпилируем, положим jar в отдельную папку, назвав его midnight.jar, и добавим в modulepath:

Теперь мы можем добавлять автоматический модуль midnight:

  1. // module-info

  2. module com.example.timeapp {

  3.     requires java.base;

  4.     requires public java.desktop;

  5.     requires midnight;

  6.  

  7.     exports com.example.timeapp.spi;

  8.  

  9.     uses com.example.timeapp.spi.TimeProvider;

  10. }


В ServiceLoader MidnightProvider не попадёт, зато мы можем создать экземпляр класса напрямую. Немного перепишем код в Main, чтобы можно было объединить провайдеры из ServiceLoader с провайдерами, инстанцируемыми прямо в коде:
  1. Stream.concat(

  2.         StreamSupport.stream(serviceLoader.spliterator(), false),

  3.         Stream.of(new MidnightProvider())) // we can directly access to class from automatic module

  4.         .forEach(t -> …


Пример на GitHub



  Вот и всё. Надеюсь у меня получилось приоткрыть завесу тайны Project Jigsaw. За вопросами добро пожаловать в комментарии.

Проект на GitHub: https://github.com/annimon-tutorials/Java-9-Jigsaw-Example
Почитать:
Project Jigsaw: Quick Start Guide
The State of the Module System
Java Platform Module System: Requirements
First steps with Java 9 and Project Jigsaw — Part 1 (перевод)
First steps with Java 9 and Project Jigsaw — Part 2 (перевод)

Проект Jigsaw в Java 9. Модулярное будущее, которого не избежать

О проекте модуляризации Java мы слышим из года в год. Каждый раз ждём, следим за анонсами. Нам говорят, что Jigsaw практически готов, но каждый раз переносят срок выхода. Может быть, это не такой простой проект, как видится многим? Возможно, что изменения в нем повлияют на разработку в корне? Может быть, модулярная система — это только верхушка айсберга? Ответить на вопросы, связанные с проектом Jigsaw, мы попросили Ивана Крылова.

Иван занимается разработкой виртуальных машин Java и компиляторов более 10 лет, в настоящее время развивает компиляторную инфраструктуру в виртуальной машине Zing в Azul Systems. Регулярный докладчик на конференциях JUG.ru и других европейских Java-конференциях.

Что такое Jigsaw, и как он повлияет на мир Java?

— Иван, добрый день. Расскажите, пожалуйста, какое влияние проект Jigsaw окажет на экосистему Java?

— Добрый. Для объяснения моего взгляда на Java и модулярность стоит немного рассказать о моей деятельности. Коммерческим программированием я занимаюсь примерно двадцать лет. Последние одиннадцать из них — разработкой виртуальных машин. Работал в команде HotSpot Runtime в Sun. Потом компания была куплена Oracle. Далее было несколько изменений деятельности, и последние три с половиной года работаю в компании Azul Systems, где занимаюсь разработкой виртуальных машин.

Всё это я рассказал к тому, чтобы вы понимали, что мой взгляд на функциональность является взглядом разработчика JVM, человека, который достаточно далёк от мира «кровавого энтерпрайза», от проблем, с которыми сталкиваются разработчики высокого уровня абстракции.

Возвращаясь к вопросу о влиянии Jigsaw на экосистему Java, я бы сказал, что изначально это влияние не так велико, как может показаться. Начнём с того, что если разработчик не хочет переходить на парадигму модулей, он может не переходить. Старые способы взаимодействия с кодом и областями видимости в целом будут работать. Проблемы начнутся тогда, когда разработчик пытается перенести свой код в модули, а другой код еще не перенесён в модульную инфраструктуру.

Другие проблемы начнутся при использовании механизма OSGi, когда захотят перейти на механизм зависимостей Jigsaw, то поддерживать два способа написания этих зависимостей может оказаться затруднительным, но это различные частные случаи. В целом, для некоторого количества разработчиков проект Jigsaw пройдёт достаточно незаметно, поэтому оценить глобально для всех, наверное одного универсального ответа не будет.

— В Java 8 ключевыми нововведениями стали лямбда-выражения и Stream API. С какими значимыми изменениями можно сравнить Jigsaw?

— Модули проекта Jigsaw не оказывают большого влияния на написание кода и на функциональную последовательность действия кода. Jigsaw отвечает за то, как прописывается взаимодействие между различными компонентами, как мы заворачиваем код в единицы абстракции. В Java 9 и не планируются новые API, которые были бы революционными или сильно меняли то, как разработчики пишут свой код сегодня.

Есть определенные эволюционные изменения: например, в Java 8 появился Optional, в Java 9 в Optional добавится несколько методов, таких, как конвертер в Stream, метод or, но это инкрементальные изменения. Почувствовали необходимость добавить — добавили. Большие приятные изменения в Process API, которые позволят узнать pid виртуальной машины, получение дерева процессов, управления процессами. Process API позволит избавиться от некоторого количества хаков, которые нужно было наворачивать: например, вызовов shell с парсингом output или другие способы, уйдёт еще одна необходимость залезать в native. Такие инкрементальные изменения разбросаны по коду class libraries, но нет одного кода в API или одного мощного синтаксического изменения.

Все семантические изменений собраны в Milling Project Coin. Самое заметное — это, наверное, появление приватных (default) интерфейсных методов. Представим ситуацию, когда два приватных интерфейсных метода содержат общий код. Раньше такой код можно было вынести только в public метод, но такой метод может не иметь смысла с точки зрения публичного API, предлагаемого данным интерфейсом. Теперь такой метод может быть объявлен private. Этот метод не будет виден как часть API этого интерфейса, зато его реализацию можно спокойно использовать внутри этих дефолтных методов. Данное изменение позволит сделать чище тот API, который вы предлагаете с помощью интерфейса и, в частности, предлагая дефолтные реализациями методов этого интерфейса.

— Проект модуляризации существует давно, около девяти лет, сроки выхода переносились не один раз. На конференции JavaOne 2015 эта тема была удостоена вниманием нескольких докладчиков (Alan Bateman, Alex Buckley, Mark Reinhold). Такой ажиотаж вокруг показывает важность проекта?

— Проект Jigsaw потянул за собой определенные изменения. Я рассматриваю Jigsaw как некие две составные части, первая — это изменение в языке модели видимости, декларации модулей, синтаксис. Вторая часть — это то, как модель модулей была применена непосредственно к class libraries в jdk. Часть классов переехала, другие стали deprecated или перестали быть видимы снаружи.

Большие дебаты были про механизм reflection. Раньше он позволял достучаться до любого приватного класса и метода. Были возможны изменения полей на лету через reflection и их приватности. То есть были доступны, я бы сказал, эксплойты, которые использовались большим количеством библиотек. В конечном итоге это бы отразилось на пользователях. Перечисленные вами архитекторы языка вышли к сообществу, чтобы рассказать о том, какие изменения будут, почему они произойдут, зачем нужно делать эти болезненные изменения. Болезненность заключается в том, что некоторое количество библиотек сразу автоматически стало несовместимым. Перевод кода на более совместимую версию у них занимает время, что, как обычно, сопровождается шумом, например, таким: «переход на девятую версию ломает всё». Эти объяснения были необходимы.

Большое количество докладов про Jigsaw в прошлом году было на JavaOne, почти такое же количество на Devoxx в Антверпене. В этом будут такие же доклады, но будут интересные от сторонних людей, например, ребята из IBM рассказывают, есть ли жизнь после перехода на модули. Я с интересом буду ждать видеозаписей с JavaOne 2016.

— Новое понятие — модуль. Как правильно трактовать «составные» части модуля: module name, exports и requires? Хотелось бы поподробней остановиться на «requires» и понять, что же мы в действительности сможем «требовать», например, «requires local» или «requires optional»?

— Давайте начнем с простого книжного примера, ситуации, когда у вас нет существующего кода, вы просто пишете его с нуля. У вас есть классы и вы разместили их в разных пакетах, решили, что у них не должны быть тесной однозначной связи, и они в различных модулях. Когда какому-то классу из первого модуля нужны классы из второго модуля, он говорит, что мне нужен, то есть requires, вот этот модуль два, указывая какие пакету ему нужны. Вся иерархия достаточно строгая, если пакет управляет абстракциями на уровне классов, то модуль управляет иерархией уровня пакет, это выражение requires.

Проверка происходит в обоих случаях: и в момент компиляции и в момент исполнения, как это довольно часто происходит и с другими элементами языка Java. JVM не может гарантировать, что все проверки были пройдены, скажем так, честно, на уровне компиляции, байткод может приходить откуда угодно, поэтому во многом проверки дублируются и в javac, и на уровне JVM Runtime. Requires проверяет, что необходимые для работы этого модуля пакеты доступны, эти пакеты находятся каком то модуле два или модуле три. Если тем, в свою очередь, нужны ещё какие-то модули, то строится такая транзитивная цепочка с тем, чтобы проверить буквально в самом начале работы, что все необходимые пакеты из соответствующих модулей нам доступны.

Затем, exports — это механизм видимости. Он говорит о том, что этот модуль предоставляет такие-то пакеты другим модулям. Можно указать, что всем сразу — это условная «*», которая показывает, что все другие модули, все, кто хочет пользоваться этим модулем, могут пользоваться пакетами. Это означает не то, что абсолютно все классы доступны пользователям, а лишь те, которые задекларированы public, т.е. те, которые ескейпятся, становятся доступны другим модулям через этот механизм. Можно указать export to когда вы хотите проэкспортировать непосредственно указанному модулю и никому больше и этот механизм статической декларации.

Кроме того, есть определенные рантайм флаги, которые позволяют в момент вызова для уже скомпилированного модуля поменять эти области видимости, с тем, чтобы не требовалось пересобирать модуль. Если бы у него не хватало экспортов для успешной работы, то можно их добавить в командной строке, но это видится как промежуточный этап, потому что, по идее, все декларации должны быть в модуле. Т.е. модуль — некая законченная единица с декларацией того, что нужно и что нет этому модулю и что он предоставляет другим модулям для использования.

В принципе сама jdk перестроена таким образом, чтобы весь набор библиотек jdk представлял набор модулей с понятными зависимостями. Результат такой, что мы можем построить некий subset jre, в которой будут только те модули, которые нам необходимы. В этой связи появилась утилита jlink, которая позволяет собрать Run-Time Image только лишь из тех модулей, которые нам нужны. Это определенное развитие идеи, которая появилась в Java 8 с профилями. В jdk 8 можно было собрать jdk под разные профили с разным количеством классов и разным количеством функциональности, но это, скажем так, статическая сборка, без проверки того, что уже реально приложению понадобится. А jlink позволит нам собрать некий переносимый Runtime, который будет состоять из виртуальной машины, модуля java.base, еще какого то количества модулей, необходимых приложению.

В одной презентации авторы книги про модули в java 9 Paul Bakker и Sander Mak приводили различные примеры. Собрали вариант программы простого текстового анализатора с необходимым рантаймом — получился дистрибутив примерно в 20 мегабайт. Когда добавили в приложение swing-окошки, то добавились новые модули, размер такого дистрибутива вырос до 50 мегабайт. Границы JDK библиотек при этом размазываются, потому что помимо модулей, которые находятся внутри jdk и которые будут поставляться вместе с jdk, также можно добавлять модули сторонних библиотек, т.е. такие дистрибутивы совершенно по разному будут скомпонованы. Это позволит по новому распространить программу, не требуется чтобы заказчик пошел и скачал новую версию jdk, а поставлять непосредственно собранным Runtime`ом.

— О проблеме «Dependency hell» можно забыть навсегда?

— Довольно часто в реальных продакшн-системах можно увидеть очень длинную строчку classpath, где перечислено большое количество jar, или каталогов, где находится скомпилированный код. Зачастую оказывается так, что в классах задекларированы одни и те же классы одних и тех же пакетов, и тогда Runtime берет тот, который раньше появился в classpath. В связи с этим очень много людей отмечали такую проблему, когда от перемены мест jar в class-пути менялось поведение программы, вот это с помощью модулей решается на корню.

Происходит это следующим образом: если у нас классы разложены по пакетам, то определённый пакет может декларировать один-единственный модуль. Поэтому если, например, у нас находятся два модуля в двух jar и оба они включают элементы пакета, то такая ситуация невозможна, потому что предполагается, что теперь пакет принадлежит одному из модулей, т.к. каждый модуль декларирует пакет однозначно. При этом Jigsaw не решает проблему версионного контроля, т.е. соответствие версий в том понимании, в которым мы привыкли видеть в package manager-ах, например, в Linux.

Задача версионного контроля модулей изначально стояла в более ранних версиях, но потом её сняли, единственное, что можно сделать — записать версию в метаданные. Можно извлечь эти метаданные, но непосредственно механизм загрузки классов никаких дополнительных проверок не делает, это отдано на откуп другим пакет менеджеров, например OSGi, которые могут быть положены поверх. Это сделано по причине вычислительных сложностей. Это достаточно сложная задача, которую не хотели отдавать classloader`у, поэтому её передали внешнему инструментарию.

— Появятся различные виды модулей: Named Modules, The Unnamed Module, Automatic Modules. Вы можете кратко рассказать о них? В чем состоят их отличия и что каждый привносит в общую структуру?

— Если вы пишите модуль «с нуля», по всем правилам, то есть положите его в каталог верхнего уровня с названием модуля, добавите файл module-info.java, в котором содержится декларация модуля, название, которое должно совпадать с названием каталога, с перечислением exports и requires. Такой модуль — это обычный именованный модуль.

Если вы живете по старинке и у вас есть классы, которые вы просто привыкли закидывать через classpath, они тоже окажутся в модуле, который называется Unnamed Module. Поэтому для модулей, которое вы не назвали, есть безымянное последнее прибежище, так как теперь девятка не сможет работать без модулей.

Есть некая промежуточная стадия, когда у вас, например, jar файл, в нем находятся какие то пакеты, вы его положили не в classpath, а в modulepath, тогда Runtime начинает представлять его себе этот файл как некий модуль, название которого совпадает с названием jar-файла, и с ним дальше можно делать похожие вещи, как с Named модулем, и ссылаться на него.

Например, если вы разрабатываете свой именованный модуль. Вы зависите от какой то библиотеки, которая ещё не была переведена в модульную структуру, то сможете положить jar с этой библиотекой в modulepath. Он станет автоматическим модулем. Теперь вы в module-info.java можете ссылаться: requires и дальше имя модуля, который был назначен ему автоматически.

И наконец, последнее изменение, которое происходит последние недели, и ещё не совсем понятно, будет ли принят это proposal или нет. Я уже сказал чуть раньше, что существует определенная проблема с reflection, если раньше можно было достучаться до чего угодно, то теперь на уровне виртуальной машины существует защита, и появляется новая концепция, которая сейчас идёт в разработке, и, возможно, попадет в стандарт, хотя это не гарантировано — weak модуль.

Weak модуль — это такое промежуточное состояние, в котором код в этом модуле может через reflection достучаться до классов, которые находятся в именованных модулях, и даже тех, которые не экспортированы правильным образом, но при этом сам такой модуль экспортировать свою функциональность наружу не может. Это было сделано специально, чтобы пройти этот переходный период, когда нам надо будет через reflection доставать, достучаться до внутреннего кода, который изначально для этого не предназначался.

Я думаю, что со временем использование weak модулей сойдет на нет, может быть, они останутся в отладочных целях. Как и Automatic модули — это промежуточный механизм, который позволяет пройти путь миграции от немодуляризованного кода к модуляризованному.

OSGi vs Jigsaw: холивара не будет

— Почему не была одобрена OSGi для модуляризации Java? Вы согласны со словами Mark Reinhold: «The OSGi module layer is not operative at compile time; it only addresses modularity during packaging, deployment, and execution. As it stands, moreover, it’s useful for library and application modules but, since it’s built strictly on top of the Java SE Platform, it can’t be used to modularize the Platform itself»? Существует проект Penrose для взаимодействия OSGi/Jigsaw. (Модульный слой OSGi не действует в момент компиляции; обращается к модулярности только во время архивации, развёртывания или выполнения. Кроме того, в том виде, в каком он (OSGi) есть, он полезен для библиотек и модулей приложений, но поскольку он создаётся строго на основе Java SE Platform и не может быть использован для модуляризации своей платформы (прим. перевод автора).

— Я специально изучил OSGi, и у меня появилось определённое мнение на счёт того, почему всё-таки не OSGi стал Jigsaw. Первый ответ на поверхности, его обозначил Mark Reinhold: сам механизм написан поверх спецификации Java SE, если мы хотим делать поставку Java SE фрагментами, то есть модулями, то механизм OSGi нам не поможет, нам необходимо что-то на более низком уровне. В целом OSGi работает через механизм class loading`а, то есть появляется сначала один общий OSGi environment classloader, потом на каждый bundle OSGi появляется по своему classloader’у, и благодаря этому происходит некое разграничение видимости классов.

Механизм Jigsaw делает аналогичную функциональность, но уже на уровне виртуальной машины, что, в свою очередь, достаточно существенно для производительности самой виртуальной машины. Не секрет, что внутри виртуальной машины класс адресуется через последовательность: classloader и дальше пакет и имя класса, через вот эту пару. И если пакета и имя класса — это конкретные строковые литералы, которые ему соответствует, то classloader — это просто референс на объект class loading’a. Таким образом проанализировать в случае persist’a, когда мы завершили виртуальную машину и потом эти данные пытаемся восстановить при следующей загрузки, этот референс теряет какой либо смысл, у classloader’а нет собственного строкового литерала. Это накладывает ограничения на оптимизацию работы вот в таком ahead of time стиле. Поэтому разработчики Jigsaw решили отказаться от базирования этого механизма видимости на механизм class loading’a, как это было сделано в OSGi. В OSGi так сделали, потому что это было единственное, что им доступно из того, что предоставляет виртуальная машина, они не могли вмешаться в механизм class resolution.

— Получается, что фактически реализация у OSGi и Jigsaw на разных уровнях.

— Да, совершенно верно. Jigsaw реализован на более низком уровне. К счастью или несчастью. Наверное, к некоторому несчастью для тех, кто уже успел задействовать OSGi. Теперь придётся как-то взаимодействовать, поддерживать два параллельных описания зависимостей, или использовать проект типа Penrose, который позволяет синхронизировать. Описание задаётся с помощью JSON-скрипта, из которого можно сгенерировать модульное описание, Jigsaw-описание зависимостей и OSGi-описание. Придётся скрещивать эти два мира, и у тех пользователей, которые используют OSGi в настоящее время, жизнь станет немножко сложнее.

— Какие изменения произойдут в работе classloader’ов?

— Изменения на самом деле не такие уж и большие. Раньше у нас был механизм, состоящий из трех ступеней, это Bootstrap classloader, Extension classloader и Application classloader, который раньше был instance of URL classloader. Изменения в Java 9 состоит в том, что Extension classloader преобразуется в Platform classloader и связано это с тем, что если раньше Extension использовался для загрузки определённого вида классов, дополнений к jdk, их было немного. В Java 9 провели определённую работу и поняли, что у большого количества модулей можно понизить привилегию, чтобы перенести загрузку из Bootstrap classloader, и делать это с помощью Platform classloader. Наконец, Application classloader, если раньше он был instance of URL classloader, теперь это instance некого внутреннего класса. Это, в частности, в определённый момент сломало Gradle, который использовал это знание, он искал instance of URL classloader — это было неправильно. Вроде как Gradle уже поддерживает Jigsaw и эта проблема там решена.

С точки зрения пользователя, если приложение не использует эти знания, представления, как это делал Gradle, ничего сильно не изменится. В противном случае необходимо будет пересмотреть механизм, как вы работаете с Application classloader’ом. Для большинства людей эти изменения пройдут незаметно.

На уровне виртуальной машины, конечно же, есть определённые изменения, проверки видимости классов — появляется новый класс ошибок.

Область, которая получает развитие — это дополнительные оптимизации, которые связаны с тем, что если класс не выходит за пределы модуля, т.е. он не экспортируется через пакет наружу, у нас появляется возможность делать Whole Program Optimization, которые раньше были недоступны, т.к. раньше класс мог появиться в неожиданный момент, целый пласт jit-оптимизации был нам недоступен. Вплоть до того, что есть определённая информация, что Oracle работает над интегрированием ahead of time компиляции и упаковки уже бинарно скомпилированного кода в jmod, это ещё один формат для хранения модулей для более быстрого старта кода, который заключён в эти модули. Но по этому поводу информации я видел пока очень мало, посмотрим, возможно, что работа еще не доведена до какого то момента.

Решаем головоломку: ошибки, предупреждения и предостережения

— Какое мнение у вас сложилось о системе предупреждений и ошибок во время компиляции?

— Не могу сказать, что у меня пока огромный опыт в разработке Jigsaw модулей, я попробовал различные примеры. Могу рекомендовать читателям взять хрестоматийный вариант с двумя модулями, где один экспортирует, а второй что-то requires. Далее попробовать скомпилировать и проверить, что всё работает, получить, например, «Hello World». А потом стоит взять и поменять что-то: убрать requires или exports. Поменять название модуля, чтобы он не совпадал с названием каталога в котором он лежит, поменять название на кириллицу, вставить символ в название, например, подчёркивание или цифры и посмотреть, что случится.

Я бы предложил попробовать вот в таком режиме, на совершенно изолированном проекте, прежде чем это сделать на своем продуктовом коде. Аналогично можно попробовать работу с IDE, сначала собрать и запустить хороший пример, а потом его сломать и посмотреть, какие ошибки появляются.

По моим представлениям ошибки фазы компиляции будут сродни таковым, когда вы пытаетесь достучаться внутри пакета к private классу откуда-то, у вас ошибка видимости. Такую диагностику javac компилятор делает для модулей, когда вы пытаетесь достучаться до класса, который не видим. В части диагностики я не вижу больших изменений, правила, конечно же, меняются, потому что поменялись правила видимости и class resolution, я не думаю, что будет сильная разница с ошибками, которые встречаются при ошибках видимости, связанные с пакетами. Т.е. это ещё один уровень абстракции, но категория ошибок точно такая же.

— В основной массе, IDE уже имеют возможность выбора языкового уровня «Jigsaw». Возможно, что часть проверок и контроля будет сделана за пользователя. Что вы думаете по этому поводу?

— Самая большая связанная с модулями сложность, как мне кажется, заключается в том, что проект идёт очень долго, само слово module очень многозначно, и попытки найти ответы на StackOverflow или в Google зачастую приводят на устаревшие тексты, поэтому очень внимательно нужно смотреть на дату ответов.

Аналогично с IDE: например, полтора года назад я открыл IDEA, и там уже встречалось слово «модуль», но подразумевались другие модули, не имеющие отношение к Jigsaw. Поэтому, если вы планируете разработку модулей в IDE, то нужно убедиться, что IDE свежая, т.к. можно нарваться на другое прочтение слова «модуль».

— Одно из определений Jigsaw — «головоломка». Насколько сложной стала разработка, на ваш взгляд?

— Если бы у меня был бы багаж знаний о модулях в других языках, или богатый опыт с OSGi, я бы, наверное, хватался за голову и говорил, почему они его так сделали, а не эдак. У меня такого опыта нет, и принять концепцию модулей, вот это новый уровень абстракции, как декларируются зависимости было достаточно просто.

В Java 8 появилась утилита jdep, она входит в стандартную поставку jdk, которая позволяет посмотреть на зависимости между пакетами и в Java 9 появляется возможность натравить вот эту утилиту на jar файл и сгенерировать module-info.java в файл, так что на него можно посмотреть глазами, подредактировать и увидеть, что какие то requires нам на самом деле не нужны или их можно заменить. Jdep дает полностью готовый module-info.java, и можно посмотреть и привести в такое состояние, в каком вы хотите его видеть, т.е. не нужно писать с нуля, анализировать все свои зависимости, jdep прекрасно с этим справляется и очень сильно в этом помогает.

Я не думаю, что работа по созданию модуля будет такой сложной, инструментарий достаточно неплохо написан, чтобы помочь разработчикам перейти на модули.



О модулярности Java 9 на Joker 2016 расскажет Sander Mak, который сейчас готовит книгу Java 9 Modularity для O’Reilly, в своём докладе Java 9 Modularity in Action.

Если вы любите «кишочки» JVM и ждете Java 9 так же, как мы, то рекомендуем вам посмотреть следующие доклады Joker 2016:


Полная программа конференции – на сайте.

Сервисы модуля Java 9 — CoderLessons.com

Проводка и поиск

В Java уже давно есть класс ServiceLoader . Он был представлен в 1.6, но с тех пор около Java 1.2 использовалась похожая технология. Некоторые программные компоненты использовали его, но использование не было широко распространено. Он может быть использован для модульности приложения (и даже больше) и для обеспечения возможности расширения приложения с помощью каких-либо плагинов, которые не зависят от времени компиляции приложения. Кроме того, конфигурация этих сервисов очень проста: просто укажите путь к классу / модулю. Мы увидим детали.

Загрузчик службы может найти реализации некоторых интерфейсов. В среде EE есть другие методы для настройки реализаций. В среде, не относящейся к EE, Spring стал вездесущим, что имеет аналогичное, хотя и не совсем то же решение, что и аналогичная, но не совсем та же проблема. Инверсия управления (IoC) и инъекции зависимостей (DI), предоставляемые Spring, являются решением для конфигурации проводки различных компонентов и представляют собой лучшую отраслевую практику, как отделить описание / код проводки от фактической реализации функций, которые классы должны выполнять.

Фактически, Spring также поддерживает использование загрузчика служб, так что вы можете связать реализацию, обнаруженную и созданную загрузчиком служб. Вы можете найти короткую и хорошо написанную статью об этом здесь .

ServiceLoader больше о том, как найти реализацию, прежде чем мы сможем внедрить ее в компоненты, которые в ней нуждаются. Младшие программисты иногда ошибочно смешивают их, и это не без причины: они сильно связаны.

Возможно, из-за этого большинство приложений, по крайней мере те, которые я видел, не разделяют проводку и обнаружение реализации. Эти приложения обычно используют конфигурацию Spring для поиска и подключения, и это просто нормально. Хотя это упрощение, мы должны жить с ним и быть счастливыми. Мы не должны разделять две функции только потому, что можем. Большинство приложений не должны разделять их. Они аккуратно сидят на простой строке XML-конфигурации приложения Spring.

Мы должны программировать на уровне абстракции, который необходим, но никогда не должен быть более абстрактным.

Да, это предложение является перефразировкой высказывания, приписываемого Эйнштейну. Если вы думаете об этом, вы также можете понять, что это утверждение — не что иное, как принцип KISS (пусть оно будет простым и глупым). Код, а не вы.

ServiceLoader находит реализацию определенного класса. Не все реализации, которые могут быть на пути к классам. Он находит только те, которые «рекламируются». (Я расскажу позже, что означает «рекламируемый».) Программа на Java не может пройти через все классы, которые находятся в пути к классам, или они могут?

Просмотр пути к классам

Этот раздел немного обходится, но важно понять, почему ServiceLoader работает так, как он работает, даже до того, как мы обсудим, как он работает.

Код Java не может запросить загрузчик классов, чтобы получить список всех классов, которые находятся в пути к классам. Вы можете сказать, что я лгу, потому что Spring просматривает классы и автоматически находит кандидатов на реализацию. Весна на самом деле обманывает. Я расскажу вам, как это происходит. Пока что признайте, что путь к классам не может быть просмотрен. Если вы посмотрите на документацию класса ClassLoader вы не найдете ни одного метода, который бы возвращал массив, поток или коллекцию классов. Вы можете получить массив пакетов, но вы не можете получить классы даже из пакетов.

Причиной этому является уровень абстракции того, как Java обрабатывает классы. Загрузчик классов загружает классы в JVM, и JVM все равно откуда. Это не предполагает, что фактические классы находятся в файлах. Есть много приложений, которые загружают классы, а не из файла. На самом деле, большинство приложений загружают некоторые классы с разных носителей. Также ваши программы, вы просто не можете знать это. Вы когда-нибудь использовали Spring, Hibernate или какой-то другой фреймворк? Большинство из этих сред создают прокси-объекты во время выполнения и загружают эти объекты из памяти, используя специальный загрузчик классов. Загрузчик классов не может сказать вам, будет ли когда-либо новый объект, созданный платформой, которую он поддерживает. В данном случае путь к классам не является статическим. Для этих специальных загрузчиков классов даже не существует classpath. Они находят классы динамически.

Ладно. Хорошо сказано и подробно описано. Но опять же: как Spring находит классы? Весна на самом деле делает смелое предположение. Предполагается, что загрузчик классов является специальным: URLClassLoader . ( Как пишет Николай Парлог в своей статье, с Java 9 это уже не так.) Он работает с classpath, который содержит URL-адреса, и может возвращать массив URL-адресов.

ServiceLoader не делает такого предположения и поэтому не просматривает классы.

Как ServiceLoader находит класс

ServiceLoader может находить и создавать экземпляры классов, которые реализуют определенный интерфейс. Когда мы вызываем статический метод ServiceLoader.load(interfaceKlass) , он возвращает «список» классов, которые реализуют этот интерфейс. Я использовал «список» между кавычками, потому что технически он возвращает экземпляр ServiceLoader , который сам реализует Iterable поэтому мы можем перебирать экземпляры классов, которые реализуют интерфейс. Итерация обычно выполняется в цикле for вызывая метод load() после двоеточия (:).

Для успешного поиска экземпляров файлы JAR, содержащие реализации, должны иметь специальный файл в каталоге META-INF/service имеющий полное имя интерфейса. Да, в имени есть точки, и нет никакого конкретного расширения имени файла, но, тем не менее, это должен быть текстовый файл. Он должен содержать полное имя класса, который реализует интерфейс в этом файле JAR.

ServiceLoader вызывает метод findResources для получения URL-адресов файлов и считывает имена классов, а затем снова запрашивает ClassLoader для загрузки этих классов. Классы должны иметь открытый конструктор с нулевым аргументом, чтобы ServiceLoader мог создавать каждый из них.

Наличие в этих файлах имен классов для совмещения загрузки классов и создания экземпляров с использованием загрузки ресурсов работает, но это не слишком элегантно.
Java 9, сохраняя надоедливое решение META-INF/services представила новый подход. С введением Jigsaw у нас есть модули, а модули имеют дескрипторы модулей. Модуль может определить сервис, который может загрузить ServiceLoader и модуль также может указать, какие сервисы ему могут понадобиться для загрузки через ServiceLoader . Этот новый способ обнаружения реализации интерфейса службы перемещается из текстовых ресурсов в код Java. Чистым преимуществом этого является то, что ошибки кодирования, связанные с неправильными именами, могут быть идентифицированы во время компиляции или во время загрузки модуля, чтобы ускорить сбой кода.

Чтобы сделать вещи более гибкими или просто сделать их бесполезно более сложными (будущее покажет), Java 9 также работает, если класс не является реализацией интерфейса службы, но имеет public static provider() метод public static provider() который возвращает экземпляр класса который реализует интерфейс. (Между прочим, в этом случае класс провайдера может даже реализовать интерфейс службы, если он этого хочет, но, как правило, это фабрика, так зачем это делать. Обратите внимание на SRP.)

Образец кода

Вы можете скачать мультимодульный проект Maven с https://github.com/verhas/module-test .

Этот проект содержит три модуля Consumer , Provider и ServiceInterface . Потребитель вызывает ServiceLoader и использует службу, которая определяется интерфейсом javax0.serviceinterface.ServiceInterface в модуле ServiceInterface и реализуется в модуле Provider . Структуру кода можно увидеть на следующем рисунке:

Файлы module-info содержат объявления:

01

02

03

04

05

06

07

08

09

10

11

12

13

14

module Provider {

    requires ServiceInterface;

    provides javax0.serviceinterface.ServiceInterface

      with javax0.serviceprovider.Provider;

}

 

module Consumer {

    requires ServiceInterface;

    uses javax0.serviceinterface.ServiceInterface;

}

 

module ServiceInterface {

    exports javax0.serviceinterface;

}

Ловушки

Здесь я расскажу вам о некоторых глупых ошибках, которые я совершил во время создания этого очень простого примера, чтобы вы могли учиться на моих ошибках, а не повторять их. Прежде всего, в документации по Java 9 в ServiceLoader которое гласит:

Кроме того, если служба не находится в модуле приложения, то объявление модуля должно иметь директиву require, которая указывает модуль, который экспортирует службу.

Я не знаю, что он хочет сказать, но то, что это значит для меня, не соответствует действительности. Может быть, я неверно истолковал это предложение, что вполне вероятно.

В нашем примере модуль Consumer использует нечто, реализующее интерфейс javax0.serviceinterface.ServiceInterface . Это что-то, на самом деле является модулем Provider и его реализацией, но оно решается только во время выполнения и может быть заменено любой другой подходящей реализацией. Таким образом, он нуждается в интерфейсе и, следовательно, должен иметь директиву require в информационном файле модуля, для которого требуется модуль ServiceInterface . Для этого не требуется модуль Provider ! Модуль Provider аналогичным образом зависит от модуля ServiceInterface и должен требовать его. Модуль ServiceInterface ничего не требует. Он экспортирует только пакет, содержащий интерфейс.

Также важно отметить, что ни модуль Provider ни модуль Consumer не обязаны экспортировать какой-либо пакет. Provider предоставляет сервис, объявленный интерфейсом и реализованный классом, названным в честь ключевого слова with в файле информации модуля. Он предоставляет этот единственный класс для всего мира и ничего больше. Чтобы обеспечить только этот класс, было бы излишним экспортировать пакет, содержащий его, и это, возможно, излишне открыло бы классы, которые могут происходить в том же пакете, но являются внутренним модулем. Consumer вызывается из командной строки с помощью параметра –m , и он также не требует, чтобы модуль экспортировал какой-либо пакет.
Команда, как запустить программу

1

2

3

4

java -p Consumer/target/Consumer-1.0.0-SNAPSHOT.jar:

  ServiceInterface/target/ServiceInterface-1.0.0-SNA

  PSHOT.jar:Provider/target/Provider-1.0.0-SNAPSHOT.

  jar -m Consumer/javax0.serviceconsumer.Consumer

и это может быть выполнено после успешной команды установки mvn . Обратите внимание, что плагин компилятора maven должен быть как минимум версии 3.6, иначе ServiceInterface-1.0.0-SNAPSHOT.jar будет находиться в пути к классам вместо пути к модулю во время компиляции, и во время компиляции не будет найдена информация о module-info.class файл module-info.class .

Какой смысл

ServiceLoader можно использовать, когда приложение подключено к некоторым модулям только во время выполнения. Типичным примером является приложение с плагинами. Я сам столкнулся с этим упражнением, когда портировал ScriptBasic для Java с Java 7 на Java 9. Интерпретатор языка BASIC может быть расширен классами, содержащими общедоступные статические методы, и их необходимо аннотировать как BasicFunction . В последней версии требовалось, чтобы хост-приложение встраивало интерпретатор в список всех классов расширений, вызывающих API в коде. Это лишнее и не нужно. ServiceLoader может найти реализацию службы, для которой интерфейс ( ClassSetProvider ) определен в основной программе, а затем основная программа может вызывать реализации службы одну за другой и регистрировать классы, возвращаемые в наборах. Таким образом, хост-приложению не нужно ничего знать о классах расширений, достаточно, чтобы классы расширений помещались в путь модуля и каждый из них предоставлял услугу.

Сам JDK также использует этот механизм для определения местоположения регистраторов. Новый Java 9 JDK содержит класс System.LoggerFinder который может быть реализован как сервис любым модулем, и если есть реализация, ServiceLoader может найти метод System.getLogger() который найдет это. Таким образом, протоколирование не привязано к JDK, не привязано к библиотеке во время компиляции. Достаточно предоставить регистратор во время выполнения и приложение, библиотеки, которые использует приложение, и JDK будут использовать одно и то же средство ведения журнала.

Со всеми этими изменениями в механизме загрузки сервисов и превращением его в язык, который не поддерживается при загрузке ресурсов, можно надеяться, что этот тип обнаружения сервисов получит импульс и будет использоваться в более широком масштабе, как он использовался ранее.

Опубликовано на Java Code Geeks с разрешения Питера Верхаса, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Java 9 Module Services

Мнения, высказанные участниками Java Code Geeks, являются их собственными.

Сандер Мак о переходе к Java-модулям — JUG.ru

Вышла Java 10 — значит, пришло время использовать Java 9! Шутки шутками, но с неторопливостью индустрии сейчас действительно впору обсуждать модули Jigsaw: шумиха вокруг них была в прошлом году, а вот по-настоящему многие задумаются уже в этом.

В новом выпуске подкаста InfoQ на вопросы о модулях ответил Сандер Мак — соавтор книги «Java 9 modularity», который на этой теме печеньку съел. А у нас на конференции JPoint он как раз выступит с темой «Designing for modularity with Java modules». И мы решили перевести на русский часть сказанного в подкасте: если вы не следили пристально за модулями, поможет быстро понять главное, а если сами будете на JPoint — станет «предисловием» к докладу.

— Java-разработчиков в мире порядка шести миллионов, но далеко не все они используют Java 9. Судя по вашим наблюдениям, насколько активно люди переходят на неё?

— Да, думаю, можно без особого риска утверждать, что из этих шести миллионов на «девятке» сейчас меньшинство. На Java 8 люди прыгали довольно быстро, потому что видели лямбды и стримы, а это очень впечатляющие и убедительные фичи, которые можно просто брать и использовать. А с Java 9 ситуация другая. Мало того, что её пришлось долго ждать, так ещё и главная фича в виде модульной системы — совсем не такая, как лямбды и стримы. Она больше про архитектуру и проектирование приложений, она влияет на используемые инструменты — в общем, это фича, которой требуется куда больше времени.

Поэтому меня не удивляет, что люди не прыгают на Java 9 так, как переходили на Java 8. Ну и ещё сказывается то, что Oracle решили изменить график выхода версий на полугодовой, так что Java 9 уже оказывается сменена Java 10. Комьюнити ещё должно принять эту новую схему, командам и людям надо разобраться, как со всем этим быть, когда уже в сентябре Java 11 выйдет. По-моему, из-за этого люди пока что заняли выжидательную позицию.

И это печально, потому что изменения между Java 8 и 9 довольно большие — и из-за модуляризации, и из-за кучи других фич, это большой релиз. Если вы до сих пор не разбираетесь с ним, по-моему, вы оказываете себе медвежью услугу. Потому что, когда вы будете-таки переходить на новую версию — будь то хоть 9, хоть 10, хоть 11 — вы всё равно столкнётесь с теми же проблемами, с которыми можете разобраться уже сейчас.

— Хорошее замечание. К словам о том, как лямбды и стримы манили разработчиков: а в случае с модуляризацией в чём вообще главная выгода? Она явно делает всё меньше, но почему это так важно?

— Ну, я не думаю, что главное в ней «делать всё меньше», я думаю, что главное — делать всё более управляемым и поддерживаемым. Модуляризация — это, по сути, архитектурный принцип, которого стоит придерживаться при работе с большими кодовыми базами. Обычно, когда команда из нескольких разработчиков начинает новый проект, стартуя с чистого листа, поначалу всё хорошо и фичи реализуются одна за другой, но через какое-то время упираешься в стену. Кодовая база становится гораздо сложнее — возможно, через год, или через два, или через четыре, но настаёт момент, когда её всё сложнее и сложнее поддерживать, добавлять новые фичи без того, чтобы где-то что-то сломалось. И в этот момент корректно модуляризовать её уже сложно.

Конечно, у Java-разработчиков и раньше были инструменты для такого. При их грамотном применении и при осознании стоящих за ними принципов можно было добиться многого. Но теперь есть «родная» модульная система, помогающая инкапсулировать код, а инкапсуляция тут важна. Если задуматься о модуляризации — она о том, чтобы прятать вещи. Если вы прячете детали реализации и выставляете наружу только API, это очень помогает быть уверенным, например, что никто не будет использовать «внутренности» неправильно. Кроме того, у модулей есть явно прописанные зависимости, они напрямую сообщают, какие ещё модули им нужны, и это знание может быть использовано тулингом — например, компилятором или рантаймом — чтобы убедиться, что всё на месте.

И это большой шаг вперёд по сравнению с привычным classpath. У JAR-файлов в classpath нет информации о зависимостях, так что можно упустить нужный файл, и это даст о себе знать уже в рантайме. А кроме того, любой публичный класс в JAR-файле виден любому другому публичному классу в classpath. Так что модуляризация и привносит инкапсуляцию, и помогает делать более поддерживаемый и надёжный софт.

— Но то, о чём вы говорите, звучит подходящим для новых проектов. А если сейчас уже есть запущенная на «восьмёрке» система, то выгода от её перехода на «девятку» неочевидна. Что могу выиграть? Будет ли быстрее запуск? Будет ли меньше поверхность атаки, раз теперь можно не включать неиспользуемые вещи вроде CORBA?

— Определённо, это больше подходит новым проектам, а мигрировать большую кодовую базу на модули зачастую сложно. Но с переходом на Java 9 даже без модулей получаешь некоторые преимущества. Например, сборщиком мусора по умолчанию стал G1GC, в котором много улучшений, и есть Compact Strings — если в вашем коде много ASCII-строк, увидите разницу.

Кроме того, как вы уже упомянули, поскольку теперь сам JDK модуляризован, и традиционно входившие в состав JDK вещи вроде CORBA получили статус deprecated и deprecated for removal («будет удалено в следующих версиях»), а кое-что вообще удалено (впервые за долгое время). Но тут есть и обратная сторона: если ваше приложение такие штуки использует, то у вас возникает проблема.

— Про «вообще удалено» — что именно, и почему Oracle именно в этой версии решились что-то удалять?

— Если правильно помню, удалили шесть методов, связанных с PropertyChangeListener. И это связано с тем, что они — часть JavaBeans, а JavaBeans попали в модуль java.desktop. Что довольно странно, но это легаси, с которым нам жить. И при этом, например, у java.util.logging.LogManager был метод AddPropertyChangeListener. Что нормально работает, когда всё в classpath — кому какое дело. Но всё перестаёт быть нормальным, когда у LogManager в JDK зависимость от десктопного модуля: он довольно большой и посвящён вещам вроде AWT и Swing. Так что команда решила удалить методы, чтобы избежать такого.

Я думаю, это хорошо показывает трейд-оффы при переходе к модулям. В вашей кодовой базе могут быть такие заковыристые зависимости, и всё работает, если просто закидывать всё в classpath. Но можно ли назвать хорошим дизайном, когда у вас реализация логирования зависит от десктопного API? Такими вопросами стоит задаваться, вот они при разработке Java 9 и задались. Думаю, при переходе к модулям у людей всплывёт много таких скрытных и на самом деле не нужных зависимостей, указывая на запутанные места, которые вообще-то стоит распутать.

— Что вы сейчас видите на практике: когда люди с существующим проектом переходят с Java 8 на 9, они остаются с classpath, или идут до конца и модуляризуют приложение?

— Я в основном вижу остающихся с classpath. Потому что это привычно людям, они это знают, и даже с этим у них в некоторых случаях возникают сложности. Если у вас приложение на Java 8, то при переходе на 9 даже без модулей будьте готовы к возможным проблемам. Например, если у вас ваш код использует API из JDK, которые теперь оказались инкапсулированы. Вопросы есть, но они решаемые.

Но, конечно, когда уже перешёл на Java 9 с classpath, то следующим вопросом становится «переходить ли теперь на модули». Нет какого-то подходящего всем сразу ответа на это. Если ваша кодовая база сама по себе уже имеет довольно модульную структуру, то использовать Java-модули вам будет и удобно, и выгодно. Но если у вас один большой JAR со спагетти-кодом, то вам перейти к модулям будет непросто. Модули — это не какая-то магическая языковая фича, где просто вставляешь несколько ключевых слов. Это сложная задача разбиения вашего кода на поддающиеся управлению независимые части. И никакая фича за вас эту работу не проделает. В общем, тут надо смотреть по ситуации и решать в каждом конкретном случае отдельно.

— Вместе с Java 9 появился ещё и инструмент jlink — можете о нём рассказать?

— Да, это действительно новый инструмент из JDK 9, и я думаю, что это очень клёвое применение модульной системы. Ситуация такая: с jlink можно взять своё модульное приложение, указать его корневой модуль, и сказать jlink создать образ рантайма под вас. Это практически кастомная JRE — содержит ваши модули, а также те и только те модули из JDK, которые нужны для запуска вашему приложению. Это, конечно, основано на метаданных, на модульных дескрипторах из вашего приложения.

Если взять для примера десктопное приложение, оно может ограничиваться модулем java.desktop, зависимостями этого модуля и кодом самого приложения. А всякого ненужного старья вроде CORBA там не будет. Если взять совсем простой Java-класс, использующий только модуль java.base (корневой модуль для JDK), тогда весь образ рантайма получится около 20 мегабайт — то есть гораздо меньше, чем рапространять с приложением JDK целиком. И ещё в этом процессе ко всем модулям могут применяться оптимизации, например, создаваться кэш всех ваших модульных дескрипторов, чтобы не приходилось парсить их при запуске.

— Выход Jigsaw в своё время оказался отложен отчасти из-за вопросов совместимости с другими модульными системами вроде OSGi и JBoss Modules. Что с этим теперь?

— На Java 9 можно запускать OSGi-модули, используя classpath, можно использовать модули JBoss. Но если хочется пойти дальше, если задумываешься о взаимодействии OSGi и Java-модулей — ну, были эксперименты, в основном со стороны OSGi Alliance, но ничего подходящего для продакшна. Пока что у Jigsaw и OSGi нет никакого реального интеропа. Они просто сосуществуют в одной сфере.

Так что, если у вас уже есть OSGi-приложение, вряд ли вы будете переписывать его на Java-модули, просто будете использовать classpath. А если начинаете с нуля новое приложение, тогда у вас есть выбор. Что стоит предпочесть, зависит от того, что вам нужно. Если хочется чего-то родного для платформы, понятно, что напрашиваются Java-модули. Но вы можете хотеть и динамики OSGi, там есть вещи, которые будет непросто реализовать поверх Java-модулей. (примечание JUG.ru: если вы столкнулись с выбором из Jigsaw и OSGi, вас может заинтересовать видеозапись доклада Никиты Липского)

— Чего ожидаете от модульной системы в будущем, уже за пределами «девятки»?

— В данный момент, насколько мне известно, нет открытых JEP, связанных с модульной системой. Это не значит, что никакой работы не происходит, но в основном косметическая. Но чего я лично жду от следующих итераций модульной системы, так это версионирования.

Сейчас модульная система может фиксировать версии модулей, но не использует это для проверок или разрешений, связанных с версиями. И для многих это звучит удивительно, потому что большинство модульных систем этим занимаются. Но при работе над Jigsaw это намеренно пометили «out of scope». У нас уже есть системы сборки вроде Maven и Gradle, есть другие модульные системы вроде OSGi, и все они делают что-то с версиями, но у всех разная семантика, разные механизмы разрешений и так далее. Просто брать и сразу помещать такое в JDK и в спецификацию языка не захотели, и я могу это понять. Но в то же время версионирование и модуляризация логично идут рука об руку, и я надеюсь, что это станет частью модульной системы в Java.

Послушать выпуск подкаста целиком можно на SoundCloud, а увидеть доклад Сандера о модулях — 7 апреля на конференции JPoint.

Первые шаги с Java 9 и проект Jigsaw – часть вторая

Здравствуйте, Хабр.

После некоторого промедления публикуем вторую часть статьи о проекте Jigsaw и Java 9, вышедшую в блоге Codecentric. Перевод первой части находится здесь.

Это вторая часть статьи для тех, кто хочет поближе познакомиться с проектом Jigsaw. В первой части мы кратко обсудили, что такое модуль, и как была модуляризована исполняющая среда Java Runtime. Затем мы рассмотрели простой пример компиляции, упаковки и запуска модульного приложения.

Здесь мы постараемся ответить на следующие вопросы:

  • Можно ли ввести ограничение на то, какие модули смогут читать экспортированный пакет?
  • Что делать с различными версиями одного и того же модуля, присутствующими в пути к модулям?
  • Как Jigsaw взаимодействует с немодульным унаследованным кодом?
  • Как собрать собственный образ исполняющей среды Java?

Возьмем за основу пример из части 1 и продолжим работать с ним. Код по-прежнему находится здесь.

Предоставление права на чтение конкретным модулям

В первой части мы говорили о том, как развивается доступность Java в рамках Jigsaw. Один из уровней доступности, который был упомянут, но не разъяснен как следует, таков: “публичный для некоторых модулей, тех, что читают этот модуль”. Так мы можем ограничить круг модулей, которым будет разрешено читать наши экспортированные пакеты. Допустим, разработчики de.codecentric.zipvalidator
терпеть не могут разработчиков de.codecentric.nastymodule
, поэтому могут изменить свой module-info.java
вот так:

module de.codecentric.zipvalidator{

    exports de.codecentric.zipvalidator.api 
        to de.codecentric.addresschecker;
}

Таким образом, лишь addresschecker
может получить доступ к API zipvalidator
. Данное указание выполняется на уровне пакетов, поэтому ничто вам не мешает ограничить доступ для одних пакетов, в то же время предоставив полный доступ для других. Такая практика именуется «квалифицированный экспорт». Если модуль de.codecentric.nastymodule
попытается обратиться к любому типу из de.codecentric.zipvalidator.api
, то возникнет ошибка компиляции:


./de.cc.nastymodule/de/cc/nastymodule/internal/AddressCheckerImpl.java:4: 
error: ZipCodeValidatorFactory is not visible 
       because package de.cc.zipvalidator.api is not visible

Обратите внимание: программа не ругается на module-info.java
, так как zipvalidator
в принципе мог бы экспортировать видимые пакеты в nastymodule
. Например, квалифицированный экспорт можно применить, когда вы хотите модуляризовать внутреннюю структуру вашего приложения, но не хотите делиться экспортируемыми пакетами внутренних модулей с клиентами.

Конфликты между версиями модулей

Часто случается так, что через транзитивные зависимости в одно и то же приложение попадают различные версии библиотеки — то есть, один и тот же модуль может дважды фигурировать в пути к модулям. Сразу приходят на ум два сценария:

  • Модули доступны во время компиляции в различных каталогах или модульных jar, но имя у них все равно одинаковое
  • Различные версии одного и того же модуля являются разноименными

Давайте попробуем скомпилировать приложение по первому сценарию. Скопировали zipvalidator
:


two-modules-multiple-versions
├── de.codecentric.addresschecker
│   ├── de
│   │   └── codecentric
│   │       └── addresschecker
│   │           ├── api
│   │           │   ├── AddressChecker.java
│   │           │   └── Run.java
│   │           └── internal
│   │               └── AddressCheckerImpl.java
│   └── module-info.java
├── de.codecentric.zipvalidator.v1
│   ├── de
│   │   └── codecentric
│   │       └── zipvalidator
│   │           ├── api
│   │           │   ├── ZipCodeValidator.java
│   │           │   └── ZipCodeValidatorFactory.java
│   │           ├── internal
│   │           │   └── ZipCodeValidatorImpl.java
│   │           └── model
│   └── module-info.java
├── de.codecentric.zipvalidator.v2
│   ├── de
│   │   └── codecentric
│   │       └── zipvalidator
│   │           ├── api
│   │           │   ├── ZipCodeValidator.java
│   │           │   └── ZipCodeValidatorFactory.java
│   │           ├── internal
│   │           │   └── ZipCodeValidatorImpl.java
│   │           └── model
│   └── module-info.java

Дублирующиеся модули находятся в разных каталогах, но имя модуля остается неизменным. Как Jigsaw реагирует на это во время компиляции?

./de.codecentric.zipvalidator.v2/module-info.java:1: 
error: duplicate module: de.codecentric.zipvalidator

Итак, здесь нам легко не отделаться. Jigsaw выдает ошибку компиляции, когда в пути к модулям присутствуют два одноименных модуля.

Что насчет второго случая? Структура каталогов остается прежней, но теперь оба zipvalidator’а получают разные имена (de.codecentric.zipvalidator.v{1|2}
), и addresschecker читает оба этих имени.

module de.codecentric.addresschecker{
     exports de.codecentric.addresschecker.api;
     requires de.codecentric.zipvalidator.v1;
     requires de.codecentric.zipvalidator.v2;
}

Скорее всего, и здесь не скомпилируется? Читать два модуля, экспортирующих одни и те же пакеты? Оказывается, скомпилируется. Я сам удивился: компилятор распознает возникшую ситуацию, но ограничивается лишь такими предупреждениями:

./de.cc.zipvalidator.v1/de/codecentric/zipvalidator/api/ZipCodeValidator.java:1: 
warning: package exists in another module: de.codecentric.zipvalidator.v2

Разработчик с готовностью проигнорирует такое предупреждение и запустит приложение. Но Jigsaw явно не нравится то, что он увидит во время выполнения:

java.lang.module.ResolutionException: 
Modules de.codecentric.zipvalidator.v2 and de.codecentric.zipvalidator.v1 export 
package de.codecentric.zipvalidator.api to module de.codecentric.addresschecker

Мне это кажется малопонятным, по-моему, ошибку времени компиляции можно было бы оформить и поаккуратнее. Я поинтересовался в рассылке, почему был выбран именно такой вариант, но на момент написания статьи ответа еще не получил.

Автоматические модули и безымянный модуль

До сих пор мы работали в полностью модуляризованной среде. Но что делать в таких весьма вероятных случаях, когда придется иметь дело с немодульными Jar-файлами? Здесь вступают в игру автоматические модули и безымянный модуль.

Начнем с автоматических модулей. Автоматический модуль — это jar-файл, поставленный в modulepath. После того, как вы его туда запишете, можно будет ответить на три следующих вопроса об этом модуле:

В: Каково его имя?
О: это имя jar-файла. Если вы поставите в путь к модулям файл guava.jar, то получите автоматический модуль guava.

Это также означает, что вы не сможете использовать Jar прямо из репозитория Maven, так как guava-18.0 не является допустимым идентификатором Java.

В: Что он экспортирует?
О: Автоматический модуль экспортирует все свои пакеты. Итак, все публичные типы будут доступны любому модулю, читающему автоматический модуль.

В: Что он требует?
О: Автоматический модуль читает все (*all*) прочие доступные модули (включая безымянный, подробнее об этом ниже). Это важно! Из автоматического модуля можно получить доступ ко всем экспортированным типам любого модуля. Этот момент нигде не нужно указывать, он подразумевается.

Рассмотрим пример. Мы начинаем использовать com.google.common.base.Strings в zipvalidator-е. Чтобы разрешить такой доступ, мы должны определить ребро считывания для автоматического модуля Guava:

module de.codecentric.zipvalidator{
       exports de.codecentric.zipvalidator.api;
       requires public de.codecentric.zipvalidator.model;
       requires guava;

 }

Для компиляции потребуется указать файл guava.jar в пути к модулям (он находится в каталоге ../jars):


javac -d . -modulepath ../jars -modulesourcepath .  $(find . -name "*.java")

Все прекрасно компилируется и запускается.

(Между прочим, было не так просто запустить эту пример. Работая со сборкой Jigsaw 86, я столкнулся с некоторыми проблемами: система ругалась по поводу зависимостей от модуля jdk.management.resource
. Я спросил об этом в рассылке, обсуждение находится здесь.

Нужно сказать, что в моем решении я не пользовался «ранней» сборкой (early access build), а собирал JDK сам. При работе с OSX Mavericks возникли еще некоторые проблемы, о чем написано в треде, пришлось изменить makefile, но в итоге я все наладил. Возможно, вам при работе со следующими релизами придется столкнуться уже с другими проблемами).

Теперь самое время познакомить вас с палочкой-выручалочкой, которая незаменима при переходе на Jigsaw. Этот инструмент называется jdeps
. Он просматривает ваш немодуляризованный код и сообщает вам о зависимостях.

Рассмотрим guava:

jdeps -s ../jars/guava.jar
Имеем такой вывод:
guava.jar -> java.base
guava.jar -> java.logging
guava.jar -> not found

Это означает, что автоматический модуль guava требует java.base
, java.logging
и … “не найдено“?! Что такое? Если убрать переключатель -s
, то jdeps
уходит с уровня модулей и спускается на шаг вниз, на уровень пакетов (список немного сокращен, так как пакетов у guava довольно много):

   com.google.common.xml (guava.jar)
      -> com.google.common.escape               guava.jar
      -> java.lang
      -> javax.annotation                                   not found

Здесь видно, что пакет com.google.common.xml
зависит от com.google.common.escape
, который расположен в самом модуле, java.lang
, который хорошо известен, и от аннотации javax.annotation
, которая не найдена. Делаем вывод, что нам нужен jar с типами JSR-305, поскольку там содержится javax.annotation
(кстати, я без них обхожусь – в моих примерах мне не требуется ни один тип из этих пакетов, и ни компилятор, ни исполняющая среда при этом не возражают).

Безымянный модуль

Итак, что же такое безымянный модуль? Вновь ответим на три вопроса:

В: Какого его имя?
О: Как вы уже догадались, имени у него нет

В: Что он экспортирует?
О: Безымянный модуль экспортирует все свои пакеты любому другому модулю. Это не означает, что его можно читать из любого другого модуля – у него нет имени, и вы не можете его требовать! Команда requires unnamed; не сработает.

В: Что он требует?
О: Безымянный модуль читает все прочие доступные модули.

Итак, если вы не можете прочитать безымянный модуль из любых ваших модулей, то в чем же суть? На этот вопрос помогает ответить наш старый знакомый — путь к классам. Любой тип, считанный из пути к классам (а не из пути к модулям), автоматически помещается в безымянном модуле — или, иными словами, любой тип в безымянном модуле загружается через путь к классам. Поскольку безымянный модуль читает все другие модули, мы можем обратиться ко всем экспортированным типам из любого типа, загруженного через путь к классам. В Java 9 использование пути к классам и пути к модулям будет поддерживаться как по отдельности, так и совместно, для обеспечения обратной совместимости. Рассмотрим несколько примеров.

Предположим, у нас есть аккуратный модуль zipvalidator, но addresschecker по-прежнему не модуляризован, у него нет module-info.java
. Структура наших исходников будет такой:

one-module-with-unnamed-ok/
├── classpath
│   └── de.codecentric.legacy.addresschecker
│       └── de
│           └── codecentric
│               └── legacy
│                   └── addresschecker
│                       ├── api
│                       │   ├── AddressChecker.java
│                       │   └── Run.java
│                       └── internal
│                           └── AddressCheckerImpl.java
├── modulepath
│   └── de.codecentric.zipvalidator
│       ├── de
│       │   └── codecentric
│       │       └── zipvalidator
│       │           ├── api
│       │           │   ├── ZipCodeValidator.java
│       │           │   └── ZipCodeValidatorFactory.java
│       │           └── internal
│       │               └── ZipCodeValidatorImpl.java
│       └── module-info.java

Теперь есть один каталог classpath, в котором содержится унаследованный код, завязанный на доступ к zipvalidator, а также каталог modulepath, содержащий модуль zipvalidator. Мы можем компилировать наши модули как обычно. Чтобы скомпилировать унаследованный код, нам потребуется предоставить информацию о модульном коде. Просто запишем ее в путь к классам:


javac -d classpath/de.codecentric.legacy.addresschecker  
  -classpath modulepath/de.codecentric.zipvalidator/ $(find classpath -name "*.java")

Все работает как обычно.

Во время исполнения перед нами открывается две возможности. А именно:

  • Записать модуль в путь к классам
  • Смешать путь к классам и путь к модулям

Выбирая первый вариант, мы, фактически, отказываемся от модульной системы. Все типы, которые мы запишем в безымянный модуль, теперь смогут свободно обращаться друг к другу.

java -cp modulepath/de.cc.zipvalidator/:classpath/de.cc.legacy.addresschecker/
    de.codecentric.legacy.addresschecker.api.Run 76185

работает точно как java-приложение, используемое вами сегодня.

С другой стороны, смешанное использование пути к модулям и пути к классам работает так:

java -modulepath modulepath  -addmods de.codecentric.zipvalidator 
    -classpath classpath/de.codecentric.legacy.addresschecker/ 
    de.codecentric.legacy.addresschecker.api.Run

Используем одновременно два переключателя: -classpath
и -modulepath
. Добавлен переключатель -addmods
– при смешивании пути к классам и пути к модулям, мы не можем просто так получить доступ к любому модулю в каталогах modulepath, а должны конкретно указать, какие из них должны быть доступны.

Этот подход также работает нормально, но здесь есть загвоздка! Помните, ответ на вопрос “чего требует безымянный модуль” — это “все другие модули”. Если мы будем использовать модуль zipvalidator через modulepath, то сможем работать лишь с его экспортированными пакетами. Все остальное приведет к IllegalAccessError во время исполнения. Поэтому в таком случае вам придется придерживаться правил системы модулей.

Создание образов исполняющей среды при помощи jlink

Достаточно примеров с модулями; появился еще один новый инструмент, заслуживающий нашего внимания. jlink
– это утилита Java 9 для создания собственных дистрибутивов JVM. Самое интересное, что, благодаря новой модульной архитектуре JDK, вы можете сами выбирать, какие модули хотите включить в этот дистрибутив! Рассмотрим пример. Если мы хотим создать образ исполняющей среды, содержащий наш addresschecker, то даем команду:

jlink --modulepath $JAVA9_BIN/../../images/jmods/:two-modules-ok/ 
    --addmods de.codecentric.addresschecker --output linkedjdk


Указываем всего три вещи:
  • Путь к модулям (в том числе, наши специальные модули и путь к каталогу jmods в вашем JDK – здесь находятся стандартные модули java)
  • Модули, которые вы хотите включить в ваш дистрибутив
  • Каталог вывода

Команда создает следующую структуру:

linkedjdk/
├── bin
│ ├── java
│ └── keytool
├── conf
│ ├── net.properties
│ └── security
│ ├── java.policy
│ └── java.security
└── lib
├── classlist
├── jli

    │   └── libjli.dylib
    ├── jspawnhelper
    ├── jvm.cfg
    ├── libjava.dylib
    ├── libjimage.dylib
    ├── libjsig.diz
    ├── libjsig.dylib
    ├── libnet.dylib
    ├── libnio.dylib
    ├── libosxsecurity.dylib
    ├── libverify.dylib
    ├── libzip.dylib
    ├── modules
    │   └── bootmodules.jimage
    ├── security
    │   ├── US_export_policy.jar
    │   ├── blacklisted.certs
    │   ├── cacerts
    │   └── local_policy.jar
    ├── server
    │   ├── Xusage.txt
    │   ├── libjsig.diz
    │   ├── libjsig.dylib
    │   ├── libjvm.diz
    │   └── libjvm.dylib
    └── tzdb.dat

Вот и все. В OSX Mavericks все это занимает примерно 47 MB. Мы также можем задействовать архивацию и удалить некоторые отладочные возможности, которые все равно не понадобятся нам в продакшене. Самый компактный дистрибутив, который мне удалось создать, получился при помощи такой команды:


jlink --modulepath $JAVA9_BIN/../../images/jmods/:two-modules-ok/bin 
    --addmods de.codecentric.addresschecker --output linkedjdk --exclude-files *.diz 
    --compress-resources on --strip-java-debug on --compress-resources-level 2

Размер дистрибутива уменьшается примерно до 18 MB, по-моему – просто великолепно. В Linux, вероятно, можно ужаться и до 13.

При вызове

/bin/java --listmods

Выводится список модулей, содержащихся в этом дистрибутиве

de.codecentric.addresschecker
de.codecentric.zipvalidator
[email protected]

Итак, все приложения, зависящие от максимального количества этих модулей, могут работать на данной JVM. Но мне не удалось получить наш основной класс для запуска этого сценария. Для этого я пошел другим путем.

Внимательный читатель мог заметить, что второй вызов делается к jlink, и путь к модулям там иной, нежели при первом вызове. Во втором случае мы указываем путь к каталогу bin. Этот каталог содержит модульные jar-файлы, и jar для addresschecker также содержит в своем манифесте информацию об основном классе. Утилита jlink использует эту информацию, чтобы добавить дополнительную информацию в bin-каталог нашей JVM:

linkedjdk/
├── bin
│   ├── de.codecentric.addresschecker
│   ├── java
│   └── keytool
...

Итак, теперь мы можем вызывать наше приложение напрямую. Красота!

./linkedjdk/bin/de.codecentric.addresschecker 76185

выводит

76185 is a valid zip code

Заключение

Вот и подошло к концу наше знакомство с Jigsaw. Мы рассмотрели ряд примеров, иллюстрирующих, что можно и чего нельзя сделать при помощи Jigsaw и Java 9. Jigsaw привносит коренные изменения, которые нельзя так запросто компенсировать при помощи лямбда-выражений или ресурсов try-with
. Весь наш тулчейн от сборочных инструментов вроде Maven или Gradle до IDE придется адаптировать к модульной системе. На конференции JavaOne Ханс Доктер из Gradle Inc. прочитал доклад о том, как можно приступить к написанию модульного кода даже на Java 8 и ниже. Gradle выполняет проверку во время компиляции и выдает отказ, если целостность модуля оказывается нарушена. Эта (экспериментальная) возможность была включена в последний релиз Gradle 2.9. Нас определенно ждут интересные времена!

Для более подробного знакомства с Jigsaw вновь рекомендую вам домашнюю страницу Jigsaw Project, особенно слайды и видео докладов о Jigsaw с последней конференции JavaOne.

Модульные приложения на Java. Как? / Блог компании Инфосистемы Джет / Хабр

То, что в Java не поддерживается модульность, известно всем. Проблема существует давно, и было немало попыток исправить ситуацию. Этой цели посвящен проект Jigsaw. В 2011 планировалось добавить поддержку модульности в Java 7, в 2014 – в Java 8, наконец, было анонсировано, что в конце июля 2017 выйдет Java 9 с возможностью модуляризации. Но что же делать в ожидании этого прекрасного мига?

OSGi спешит на помощь


Для создания по-настоящему модульной архитектуры на языке Java можно воспользоваться OSGi (Open Services Gateway Initiative) – спецификацией динамической модульной системы и сервисной платформы для Java-приложений (разработчик OSGi Alliance). Она описывает модель разработки ПО, при которой компоненты обладают тремя основными признаками модульного приложения. Речь об инкапсуляции (каждый модуль скрывает свою реализацию от внешнего окружения), слабой связности (модули взаимодействуют только с помощью заранее оговоренных контрактов) и динамичности (компоненты можно замещать на лету, без остановки всего приложения). Основой концепции OSGi являются 2 сущности: наборы (Bundles) и сервисы (Services).

Bundles


При разработке ПО на Java, как правило, используются сторонние библиотеки. В мире Java-библиотеки упаковываются в файлы с расширением JAR (Java ARchive) ‒ обыкновенный ZIP-архив, содержащий Java-классы (файлы с расширением .class). При этом использование библиотек может быть сопряжено с определенными сложностями.

Как только вы начинаете использовать какую-либо библиотеку, вам, как правило, становятся доступны все классы в ней. Дело в том, что разработчики библиотек не имеют возможности спрятать классы, которые используются для реализации их внутренней логики. Таким образом, если вы используете код, который не предполагался для применения вне библиотеки, вы можете столкнуться с несовместимостью при использовании новой версии библиотеки или нарушить ее корректное функционирование.

Другая проблема – так называемый JAR Hell, об который разработчики сломали немало копий. Суть его в следующем: как только вы начинаете использовать разные версии одной библиотеки (такое случается в больших проектах, которые эволюционируют со временем), вы можете столкнуться с тем, что один и тот же класс имеет разные методы в разных версиях библиотеки. Java же устроена так, что будет использована первая версия библиотеки, которую найдет загрузчик классов. Тем самым, обратившись в коде к какому-либо классу во время выполнения программы, вы получите ошибку, что метод, к которому вы обращаетесь, не существует. Связано это с тем, что на этапе выполнения Java ничего не знает о версии библиотеки, которая должна использоваться в том или ином случае.

Разработчики OSGi не стали менять структуру JAR-файлов для обеспечения модульности, а просто добавили в них дополнительную информацию, которая используется средой OSGi. Причем, информация эта никак не влияет на использование JAR-файлов в обычных Java-приложениях. Итак, чтобы JAR-файл стал OSGi-набором, в него добавляются данные, которые определяют Export-Package (пакеты этого набора, доступные для использования вне его) и Import-Package (пакеты других наборов, требующиеся для работы этого набора). При этом возможно задать как версию API, которую набор предоставляет для других наборов, так и версию или диапазон версий API, которые набор требует для своей работы от них же. Все классы набора, которые не находятся в его экспортируемой секции, не доступны вне набора (Private). Таким способом OSGi-набор выполняет требование слабой связности.

Сегодня большинство Java-библиотек уже OSGi ready, т.е. содержат информацию для возможности выполнения в OSGi-контейнере. Кроме того есть немало инструментов и утилит, с помощью которых можно создать модули для OSGi из обычных JAR-файлов.

Рис. 1.


В OSGi можно легко избегать ситуаций JAR Hell, так как зависимости между наборами и интерфейсы, предоставляемые этими наборами, имеют четкие версии. Можно динамически загружать и выгружать новые наборы во время выполнения. OSGi отслеживает зависимости между наборами и динамически разрешает их.

Services


Итак, с помощью OSGi-наборов мы можем разрабатывать модульные приложения, которые взаимодействуют посредством интерфейсов (API). Но где взять класс, который реализует требуемый интерфейс? Вариант «добавить в API набора» не подходит – в рамках модульной архитектуры мы договорились не использовать внутренние классы наборов вне этих наборов. Другое решение ‒ применить шаблон «фабрика», реализуя интерфейс, и добавить его в API набора. Но и оно не слишком удачно, т.к. для сокрытия реализации интерфейса придется каждый раз разрабатывать новый класс.

Поиск реализации интерфейса в OSGi осуществляется с помощью реестра сервисов. В этом реестре набор может зарегистрировать реализацию с описывающим её интерфейсом. Набор, использующий интерфейс из другого набора, может найти в реестре требуемую реализацию нужного ему интерфейса. Как правило, наборы регистрируют сервисы при запуске в OSGi-контейнере. Плюс ко всему в реестре сервисов могут быть зарегистрированы одни и те же интерфейсы с разными реализациями и дополнительными идентификационными данными. Используя фильтрацию, набор может выбрать в реестре наиболее подходящий из представленных сервисов.

Рис. 2.


Микросервисы Java Virtual Machine


Микросервисная архитектура представляет собой набор независимых модулей – отдельных приложений. Взаимодействие между ними происходит посредством четко определенных интерфейсов с применением легковесных протоколов (REST, Protocol Buffers, MQ и т.д.). По факту каждый модуль – это микросервис, выполняющий одну конкретную задачу и содержащий, как правило, минимальное количество кода. Преимущества этого подхода к разработке ПО:
  • легкость (при разработке микросервиса реализуется лишь одна часть функциональности программы).
  • простота замены (если сервис не справляется со своей задачей, его можно переписать и заменить на новую версию без остановки текущего ПО).
  • повторное использование (микросервис может многократно использоваться там, где он подходит).

Разработчики модульных приложений с применением OSGi давно пользуются всеми этими преимуществами, но только в рамках виртуальной машины Java. Каждый набор с OSGi, который публикует сервис в реестр, является микросервисом внутри Java Virtual Machine, JVM (в мире OSGi такие микросервисы называются µServices).

Red Hat JBoss Fuse


Мы в «Джете» используем все преимущества OSGi при разработке ПО для телеком-операторов, страховых, процессинговых компаний. Для этого мы применяем Red Hat JBoss Fuse (конфигурация Fuse Fabric). Эта платформа предоставляет гибкую OSGi-среду для выполнения модульного приложения.

Непрерывность работы приложения, легкое горизонтальное масштабирование, простота развертывания, наличие средств управления кластером для кластерного ПО – все эти возможности доступны в Fuse Fabric. Технология позволяет развернуть несколько экземпляров Red Hat JBoss Fuse и объединить их в кластер, а также предоставляет централизованный инструмент управления получившейся средой.

В рамках Fuse Fabric существуют следующие абстракции:

Фичи (features) – совокупность OSGi-наборов, которые реализуют какой-либо функционал.
Профили (profiles) – совокупность фич, которые должны выполняться в рамках одного контейнера, и конфигурационные настройки для наборов, которые входят в фичу.
Контейнеры (containers) – отдельные JVM-процессы, которые выполняются на конкретном узле кластера Fuse Fabric под управлением контейнера Red Hat JBoss Fuse.

Рис. 3.


Любое ПО на базе технологии Fuse Fabric состоит из OSGi-наборов, которые группируются в фичи и развертываются в рамках какого-либо отдельного JVM-процесса на одном или нескольких узлах кластера в соответствии с заранее заданным профилем.

Среда Fuse Fabric дает множество инструментов для легкого управления полученным кластером. Можно создавать профили (а на их основе ‒ контейнеры), создавать/удалять/запускать/останавливать контейнеры на любом хосте, входящем в кластер, подключать новые узлы кластера и т.д. И все это online – без прерывания функционирования остальных узлов кластера. Есть возможность хранить несколько версий профилей, фич, OSGi-наборов.

Благодаря технологии Distributed OSGi, в полученной среде OSGi-наборы могут взаимодействовать в рамках как одного, так и разных контейнеров и даже разных хостов. Дополнительно (при минимальных затратах на разработку) каждый сервис, который предоставляет OSGi-набор, можно превратить в REST/web-сервис, который можно вызывать из внешних систем.

Таким образом, используя Fuse Fabric, мы можем создавать микросервисную архитектуру, которая поддерживает простоту настройки и развертывания, изолированное выполнение сервисов в рамках своих JVM-процессов (со своими специфичными настройками, например, разными настройками сборщика мусора), при этом сервисы взаимодействуют между собой по сети с использованием легковесного протокола.

Т.к. продукт Red Hat JBoss Fuse основан на Open Source стеке технологий Apache ServiceMix, в нашем распоряжении есть такие мощные технологии, как Apache ActiveMQ (реализация спецификации JMS), Apache Camel (реализация шаблонов интеграции корпоративных приложений), Apache CXF (библиотека для разработки REST/web-сервисов), Blueprint (предоставляет возможности внедрения зависимостей), Spring и т.д. Поскольку указанные технологии бесшовно интегрируются между собой в Red Hat JBoss Fuse, это значительно снижает время разработки требуемого функционала.

Материал подготовлен экспертами Центра программных решений компании «Инфосистемы Джет».

JAVA 9. Что нового? / Блог компании OTUS. Онлайн-образование / Хабр

Java — удивительно универсальный язык программирования. На сегодняшний день насчитывается около 6.5 млн разработчиков Java по всему миру. И все благодаря его утилитарности, простоте использования, безопасности и независимости платформы (он работает вне зависимости от того, какое оборудование или операционная система используются).

Java была разработана в 1995 году компанией Sun Microsystems и изначально создавалась для интерактивного телевидения и программирования бытовых электронных устройств. Тем не менее, публичный релиз Java 1.0 выявил смену фокуса на интернет-использование, превратив его в один из самых популярных языков программирования.

Очевидно, со времен 1.0 многое изменилось и вот наконец после множества переносов 21 сентября 2017 года состоялся релиз Java 9.

Особенности предыдущей (8-й) версии благодаря своей универсальности предоставили разработчикам возможность создавать решения для самых разных секторов бизнеса, включая финтех, здравоохранение и другие индустрии. Среди ключевых нововведений Java 8 следует отметить лямбда-выражения, стримы и изменения в API.

В свою очередь Java 9 также изобилует разнообразием обновлений “под капотом” и в API. Кроме очевидного проекта Jigsaw, на который возложена обязанность по внедрению модулярности в основные компоненты Java, новая версия может похвастаться ещё тремя важными фичами:

  1. Полная поддержка клиента HTTP 2.0: Вопрос в скорости, и HTTP 2.0 предоставляет более высокие результаты, колеблющиеся от 11.81% до 47.7% по сравнении с клиентом HTTP 1.1.
  2. Jshell: Новый инструмент командной строки. Если разработчик хочет автономно запустить несколько строк Java, то это можно выполнить без необходимости заворачивать все в отдельный метод или проект.
  3. Microbenchmark: Теперь производительность отдельных небольших частей кода можно измерить стандартизированным методом. Анализ JMH за наносекунды уникален для Java 9.



С полным списком советуем ознакомиться на портале сообщества Java, а также свободно скачать дистрибутив для ознакомления.

Далее мы ближе рассмотрим несколько фич, отсутствовавших в Java 8.

HTTP/2 клиент

Это, пожалуй, самое ожидаемое нововведение — HTTP клиент с поддержкой как HTTP/2 протокола, так и WebSocket. Ведь SPDY протокол от Google, на базе которого основан HTTP/2 уже демонстрирует улучшенную производительность наравне с Netty или Jetty, поддержку неблокирующего режима (много потоков на запрос/ответ), а также Push-поддержку сервера, позволяющую отправлять ресурсы клиенту без явного запроса.

Новый API представлен как первый инкубаторный модуль для Java — экспериментальная фича, которая при должном развитии официально появится в следующей версии (то есть Java 10), либо будет удалена. Необычный концепт для Java, который поощряет эксперименты, но при этом не нарушает обратной совместимости.

Проект Jigsaw

Проект Jigsaw (в переводе “головоломка”) направлен на модуляризацию Java. Это значит, что программный код разбивается на части и организуется по модулям в зависимости от задач, которые эти модули выполняют. Это позволяет использовать модули повторно, упрощает их организацию и отладку. Что ведет к оптимизированной и отлаженной разработке ПО. Это ключевое отличие Java 9 от Java 8.

Второе большое преимущество — Java Platform становится легче и более масштабируемой. Это позволяет Java-приложениям работать даже на устройствах с низкой производительностью (так как для работы требуется меньше ресурсов). Это большое изменение особенно в контексте интернета вещей (IoT). Шаг вперед для Java, и шанс увидеть больше IoT-приложений, написанных на этом языке.

Jshell

Jshell добавит встроенную поддержку и популяризирует подход Java к REPL (Read-Eval-Print-Loop). Это интерактивный инструмент, позволяющий тестировать небольшие части кода без необходимости создавать новые классы. Он оснащен функциями истории и автозаполнения, а также рядом других особенностей, включая сохранение и загрузку написанных выражений.
Скажем, если захочется запустить несколько строк Java самостоятельно, то теперь не придется заворачивать их в отдельный проект или метод. Что касается точек с запятой — можно забыть про них: Существуют различные альтернативы наподобие плагинов REPL для популярных IDE или веб-консоли Java REPL, но ни одна из них не является официальной.

Унифицированное протоколирование JVM

Добавлена единая система протоколирования всех JVM-компонентов. Тем не менее, если мы посмотрим на плюсы и минусы Java 9, вот один из них. Вызовы протоколирования отдельных компонентов JVM отсутствуют, также как и протоколирование Java-кода в Java Development Kit

G1 — дефолтный сборщик мусора

Очень часто сталкиваемся с заблуждением, что в Java есть только один сборщик мусора, хотя по факту их 4. Parallel / Throughput Collector считался дефолтным в прошлых версиях, но теперь его заменил G1, который был представлен в Java 7 и был разработан для лучшей поддержки куч размером более 4GB. Он вызывает меньше GC пауз, но если они все же происходят, то длятся дольше.

Изображения с мульти-разрешением

Этот API позволяет инкапсулировать набор изображений с разными разрешениями в единый объект. Таким образом, разработчик может получить изображение с определенным разрешением или все варианты внутри одного.

Примечательные обновления API: Concurrency и Stack Walking

Java 9 получил обновление CompletableFuture и java.util.concurrent.Flow. Flow — это Java-реализация Reactive Streams API. Reactive Streams помогают решить проблему back-pressure — накопления большого количества данных, которое происходит, когда скорость получения входящих событий выше, чем скорость их обработки приложением, что приводит к появлению буферов необработанных данных. Кроме того, в рамках улучшения concurrency, CompletableFuture получил обновление, которое решило проблемы, выявленные после его внедрения в Java 8. Оно включает поддержку задержек и тайм-аутов, улучшенную поддержку подклассов и несколько полезных методов.

Также хочется упомянуть о Stack Walking API. Все верно, Java 9 меняет подход к работе со стек-трейсами, предоставляя официальный способ обработки стек-трейсов, вместо приравнивания их к простому тексту.

Immutable List, Set, Map, и Map.Entry API

Создание множеств из нескольких элементов в Java 8 требовало нескольких строк кода. Теперь это можно осуществить только одной. Таким образом, один из самых времязатратных недостатков Java 8 был устранен. Это должно облегчить работу с массивами и ускорить некоторые функции.

Преимущества Java 9

Детали отличий Java 9 от Java 8 представляют большой интерес для разработчиков, а следовательно и для бизнеса, находящегося в поиске наилучших решений. Нововведения девятой версии улучшают следующие аспекты разработки:

  • Скорость выше благодаря поддержке клиента HTTP/2.
  • Приложения становятся ресурсоэффективней, потому что разработчики могут использовать только необходимые модули, а не всю JRE.
  • Разработка ускоряется благодаря системе модулей, позволяющей их повторное использование, упрощенную отладку и управление.
  • Появляется возможность анализа производительности очень маленьких частей кода благодаря Microbenchmarks.
  • Множества создаются одной строчкой кода.

THE END

Вот такая выжимка всех интересных изменений, которые мы отметили после выхода новой версии. Правда, теперь главный вопрос в том, когда компании начнут внедрять её, учитывая, что встречаются такие, кто ещё и восьмую версию не использует. В итоге пока решили не менять программу курса (не считая мелкой косметики) и оставить всё как есть, а уже в следующем году задуматься насчёт этого.

Модули Java 9 — JournalDev

Модули Java 9 — одно из самых больших изменений в структуре java. Здесь я собираюсь дать краткое введение на высоком уровне о «Модульной системе Java 9».

Модули Java 9

Мы рассмотрим следующие темы о системе модулей Java 9.

  1. Введение
  2. Java SE 9: Jigsaw Project
  3. Проблемы текущей системы Java?
  4. Преимущества модульной системы Java SE 9
  5. Сравните JDK 8 и JDK 9
  6. Что такое модуль Java 9?
  7. Материнская система модулей Java 9
  8. Сравните приложения Java 8 и Java 9

Введение

Корпорация Oracle перенесла выпуск Java 9 с марта 2017 года на сентября 2017 года .

Как мы знаем, Java SE 8 поставляется с тремя большими новыми функциями (плюс еще несколько улучшений и новых функций).

  1. Лямбда-выражения
  2. Stream API
  3. Date API

Точно так же Java SE 9 поставляется с тремя большими функциями (плюс еще несколько улучшений и новых функций):

  1. Система модулей Java (проект Jigsaw)
  2. Java REPL
  3. Milling Project Coin

В этом посте мы обсудим основы проекта Oracle Jigsaw.

Java SE 9: Jigsaw Project

Jigsaw Project представляет совершенно новую концепцию Java SE 9: Модульная система Java .

Это очень большой и престижный проект от Oracle Corp в версии Java SE 9. Изначально они начали этот проект как часть Java SE 7 Release. Однако с огромными изменениями он был перенесен на Java SE 8, а затем снова отложен. Сейчас он собирается выпустить с Java SE 9 в сентябре 2017 года.

Основные цели Jigsaw Project:

  • Модульный JDK
  • Как мы знаем, текущая система JDK слишком велика.Поэтому они решили разделить сам JDK на небольшие модули, чтобы получить ряд преимуществ (мы скоро обсудим их в следующих разделах).

  • Модульный исходный код
  • Текущие файлы jar исходного кода слишком велики, особенно rt.jar слишком велик, верно. Поэтому они собираются разделить исходный код Java на более мелкие модули.

  • Модульные образы времени выполнения
  • Основная цель этой функции — «Реструктуризация образов времени выполнения JDK и JRE для размещения модулей».

  • Инкапсулировать большинство внутренних API
  • Основная цель этой функции — «Сделать большинство внутренних API JDK недоступными по умолчанию, но оставить доступными несколько важных, широко используемых внутренних API».

  • Модульная система платформы Java
  • Основная цель этой функции — «позволить пользователю создавать свои модули для разработки своих приложений».

  • jlink: Java Linker
  • Основная цель этого инструмента jlink — «Позволить пользователю создавать исполняемые файлы для своих приложений».

Если вы не совсем понимаете эти вещи, НЕ волнуйтесь. Мы подробно обсудим эти концепции с некоторыми полезными примерами в следующих разделах, а также в моих следующих публикациях.

Проблемы текущей системы Java?

В этом разделе мы обсудим «Зачем нам нужна модульная система Java SE 9», что означает проблемы текущей системы Java.

Системы Java SE 8 или более ранней версии имеют следующие проблемы при разработке или доставке приложений на основе Java.

  • Поскольку JDK слишком велик, его сложно масштабировать до небольших устройств. Java SE 8 представила 3 ​​типа компактных профилей для решения этой проблемы: compact1, compact2 и compact3. Но это не решает эту проблему.
  • Файлы JAR, такие как rt.jar и т. Д., Слишком велики для использования в небольших устройствах и приложениях.
  • Поскольку JDK слишком велик, наши приложения или устройства не могут поддерживать лучшую производительность.
  • В текущей системе Java нет строгой инкапсуляции, потому что модификатор «общего» доступа слишком открыт. Каждый может получить к нему доступ.
  • Поскольку JDK, JRE слишком велик, его сложно тестировать и поддерживать приложения.
  • Поскольку публика слишком открыта, они не должны избегать доступа к некоторым внутренним некритическим API, таким как sun.*, * .internal. * и т. д.
  • Поскольку пользователь также может получить доступ к внутренним API, безопасность также является большой проблемой.
  • Приложение слишком велико.
  • Немного сложно поддерживать меньшую связь между компонентами.

Чтобы решить все эти проблемы, Oracle Corp собирается выпустить систему Java Module в Java SE 9 Release.

Преимущества модульной системы Java SE 9

Модульная система Java SE 9 обеспечит следующие преимущества

  • Поскольку Java SE 9 разделит JDK, JRE, JAR и т. Д. На более мелкие модули, мы можем использовать любые модули мы хотим.Так что масштабировать Java-приложение до малых устройств очень просто.
  • Простота тестирования и ремонтопригодность.
  • Поддерживает лучшую производительность.
  • Поскольку общедоступный — не только общедоступный, он поддерживает очень сильную инкапсуляцию. (Не волнуйтесь, это большая концепция. Скоро мы рассмотрим ее на некоторых полезных примерах).
  • Мы больше не можем получить доступ к внутренним некритическим API.
  • Модули могут очень безопасно скрывать нежелательные и внутренние детали, мы можем улучшить безопасность.
  • Приложение слишком мало, потому что мы можем использовать только те модули, которые нам нужны.
  • Легко поддерживать Меньше связи между компонентами.
  • Поддерживать принцип единой ответственности (SRP) легко.

Скоро мы рассмотрим все эти концепции одну за другой.

Сравните JDK 8 и JDK 9

Мы знаем, что содержит программное обеспечение JDK. После установки программного обеспечения JDK 8 мы можем увидеть несколько каталогов, таких как bin, jre, lib и т. Д. В домашней папке Java.

Однако Oracle Corp немного изменила структуру папок, как показано ниже.

Структура папки JDK 8:

Структура папки JDK 9:

Здесь JDK 9 НЕ содержит JRE. В JDK 9 JRE выделена в отдельную папку распространения. Программное обеспечение JDK 9 содержит новую папку «jmods». Он содержит набор модулей Java 9, как показано ниже.

В JDK 9 без rt.jar и без tools.jar

ПРИМЕЧАНИЕ: —
На сегодняшний день «jmods» содержит 95 модулей.В финальном выпуске он может увеличиться.
папка «jmods» доступна по адресу $ {JAVA_HOME} / jmods. Они известны как модули JDK.

Что такое модуль Java 9?

Модуль — это самоописывающая коллекция кода, данных и некоторых ресурсов. Это набор связанных пакетов, типов (классов, абстрактных классов, интерфейсов и т. Д.) С кодом, данными и ресурсами.

Каждый модуль содержит только набор связанного кода и данных для поддержки принципа единой ответственности (функциональности) (SRP).

Основной целью модульной системы Java 9 является поддержка модульного программирования на Java.

Мы обсудим «Что такое дескриптор модуля» и «Как разрабатывать модули Java» в моих следующих публикациях.

Мать системы модулей Java 9

На данный момент модульная система Java 9 имеет 95 модулей в раннем доступе JDK. Корпорация Oracle разделила jar-файлы JDK и спецификации Java SE на два набора модулей.

  • Все модули JDK начинаются с «jdk.* »
  • Все модули спецификаций Java SE начинаются с« java ». *

Модульная система Java 9 имеет модуль « java.base ». Он известен как базовый модуль. Это независимый модуль и НЕ зависит от каких-либо других модулей. По умолчанию все остальные модули зависят от этого модуля.

Вот почему модуль «java.base» также известен как «Мать модулей Java 9».

Это модуль по умолчанию для всех модулей JDK и модулей, определяемых пользователем.

Сравните приложения Java 8 и Java 9

Мы уже разработали множество приложений Java с использованием Java 5, 6,7 или 8.Мы знаем, как выглядят приложения Java 8 или более ранней версии и что они содержат.

Вкратце, я изобразил приложения Java 8 на диаграмме, как показано ниже:

В приложениях Java 8 или более ранних версиях компонент верхнего уровня — это пакет. Он группирует набор, относящийся к типам, в группу. Он также содержит набор ресурсов.

Приложения Java 9 не имеют большой разницы с этим. Он только что представил новый компонент под названием «Модуль», который используется для группирования набора связанных пакетов в группу.И еще один новый компонент — Module Descriptor («module-info.java»). Это оно.

Остальная часть приложения такая же, как и в более ранних версиях приложений, как показано ниже.

Подобно тому, как приложения Java 8 имеют пакеты как компоненты верхнего уровня, приложения Java 9 имеют модуль как компоненты верхнего уровня.

ПРИМЕЧАНИЕ: —
Каждый модуль Java 9 имеет один и только один модуль и один дескриптор модуля. В отличие от пакетов Java 8, мы не можем создать несколько модулей в один модуль.

Вкратце я могу сказать, что модуль Java 9 содержит следующие основные компоненты:

  • Один модуль
  • Имя модуля
  • Дескриптор модуля
  • Набор пакетов
  • Набор типов и ресурсов

Здесь ресурсы могут быть модулем -info.java (Дескриптор модуля) или любые другие свойства или XML.

В этом посте мы обсудили слишком много теории. Думаю, этого достаточно, чтобы понять основы модульной системы Java 9. В моем следующем посте мы начнем базовую разработку модуля.Я буду обсуждать еще некоторые теоретические концепции или терминологию модульной системы Java 9 по мере необходимости.

Это все, что касается темы «Введение в модульную систему Java 9». Мы обсудим еще несколько концепций модульной системы Java SE 9 в моих следующих публикациях.

Пожалуйста, напишите мне, если вам понравился мой пост или у вас есть вопросы / предложения / ошибки типа.

Спасибо за чтение моих руководств.

Счастливого обучения Java SE 9!

Ссылка: Project Jigsaw

.

Что такое открытый модуль в Java 9 и как его использовать

Переполнение стека
  1. Около
  2. Продукты
  3. Для команд
  1. Переполнение стека Общественные вопросы и ответы
  2. Переполнение стека для команд Где разработчики и технологи делятся частными знаниями с коллегами
  3. Вакансии Программирование и связанные с ним технические возможности карьерного роста
  4. Талант Нанимайте технических специалистов и создавайте свой бренд работодателя
  5. Реклама Обратитесь к разработчикам и технологам со всего мира
  6. О компании
.

Как мне назвать мой модуль Java 9?

Переполнение стека
  1. Около
  2. Продукты
  3. Для команд
  1. Переполнение стека Общественные вопросы и ответы
  2. Переполнение стека для команд Где разработчики и технологи делятся частными знаниями с коллегами
  3. Вакансии Программирование и связанные с ним технические возможности карьерного роста
  4. Талант Нанимайте технических специалистов и создавайте свой бренд работодателя
  5. Реклама Обратитесь к разработчикам и технологам со всего мира
  6. О компании
.

Сколько безымянных модулей создано в Java 9?

Переполнение стека
  1. Около
  2. Продукты
  3. Для команд
  1. Переполнение стека Общественные вопросы и ответы
  2. Переполнение стека для команд Где разработчики и технологи делятся частными знаниями с коллегами
  3. Вакансии Программирование и связанные с ним технические возможности карьерного роста
  4. Талант Нанимайте технических специалистов и создавайте свой бренд работодателя
  5. Реклама Обратитесь к разработчикам и технологам со всего мира
  6. О компании
.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *