[JDK 11] О модульной системе Java достаточно этой статьи

Java Архитектура
[JDK 11] О модульной системе Java достаточно этой статьи

После выпуска Java 8 в марте 2014 г., по прошествии 4 лет, в сентябре 2018 г. в соответствии с графиком была выпущена Java 11 с двумя промежуточными версиями Java 9 и Java 10, отличными от LTS (долгосрочная поддержка). Как последняя версия LTS, по сравнению с Java 8, Java 11 включает систему модулей, переключение на G1 в качестве алгоритма GC по умолчанию, реактивный поток потока, новую версию HttpClient и многие другие функции. В этой статье, первой из серии обновлений JDK 11, будет представлена ​​самая важная особенность этого обновления — модульная система.

1 Введение в модульную систему

Если сравнивать Java 8 с монолитным приложением, то после внедрения модульной системы, начиная с Java 9, Java превратилась в микросервис. Модульная система, код проектаJigsaw, впервые предложенный в августе 2008 г. (лучше, чем Мартин ФаулерпредложитьМикросервисы все еще на 6 лет раньше), официально вступили в стадию разработки с Java 9 в 2014 году и, наконец, были выпущены в сентябре 2017 года с Java 9.

Так что же такое модульная система? ОфициальныйопределениедаA uniquely named, reusable group of related packages, as well as resources (such as images and XML files) and a module descriptor.какРисунок 1Как показано, носителем модуля является jar-файл, а модуля — jar-файл, но по сравнению с традиционным jar-файлом корневой каталог модуля имеет еще одинmodule-info.classфайл, то естьmodule descriptor.module descriptorОн содержит следующую информацию:

  • имя модуля
  • От каких модулей вы зависите
  • Какие пакеты в экспортируемом модуле (что позволяет напрямуюimportиспользовать)
  • Какие пакеты в открытом модуле (разрешить доступ через отражение Java)
  • Какие услуги предоставляются
  • Какие услуги полагаться на

Рисунок 1: Модуль Java 9

То есть любой файл jar, просто добавьте юридическийmodule descriptor, его можно обновить до модуля. Каковы преимущества этого, казалось бы, небольшого изменения? На мой взгляд, есть как минимум четыре преимущества.

Во-первых, собственное управление зависимостями. Благодаря модульной системе Java можетmodule descriptorРассчитайте зависимости между различными модулями, и как только круговая зависимость будет найдена, запуск завершится. В то же время, поскольку модульная система не позволяет разным модулям экспортировать один и тот же пакет (т.split package, разделить пакет), поэтому при поиске пакета Java может точно найти модуль, чтобы повысить производительность.

Во-вторых, оптимизировать JRE. После введения модульной системы сам JDK был разделен на 94 модуля (см.фигура 2). Добавлено с Java 9jlinkTools разработчики могут свободно комбинировать эти модули в соответствии с реальными сценариями приложений, удалять ненужные модули и создавать собственные JRE, тем самым эффективно уменьшая размер JRE. Благодаря этому размер JRE 11 всего на 53 % меньше размера JRE 8, уменьшившись с 218,4 МБ до 116,3 МБ, так сильно оклеветанного гигантского jar-файла в JRE.rt.jarтакже удалены. Меньший размер JRE означает меньший объем памяти, что делает Java более удобной для разработки встраиваемых приложений.

Рисунок 2: Модульный JDK

В-третьих, лучшая совместимость. С момента рождения Java было всего 4 вида видимости пакетов, что сильно снижает поддержку в Java инкапсуляции, одной из трех основных объектно-ориентированных фич.Или странное именование, чтобы подчеркнуть, что те или иные классы предназначены только для внутреннего использования, используйте их на свой страх и риск. После Java 9 используйтеmodule descriptorсерединаexportsКлючевые слова: сопровождающие модулей могут точно контролировать, какие классы можно использовать снаружи, а какие только внутри, другими словами, они больше не полагаются на документацию, а гарантируются компилятором. Уточнение видимости классов, в дополнение к лучшей совместимости, также повышает безопасность.

Рисунок 3: Доступность Java

В-четвертых, повысить эффективность разработки языка Java. После Java 9 Java как будто зависает, меняя первоначальный стиль задержки и задержки, и строго следуя стратегии выпуска мажорной версии каждые полгода, с сентября 2017 по март 2020, с Java 9 на Java 14, 6 версий были освобождены через три года без каких-либо задержек, см.Рисунок 4. Это, несомненно, во многом связано с введением модульной системы. Как упоминалось ранее, после Java 9 JDK был разбит на 94 модуля, каждый из которых имел четкую границу (module descriptor) и независимые модульные тесты, для каждого разработчика языка Java всем нужно обращать внимание только на модули, за которые они отвечают, поэтому эффективность разработки значительно повышается. Разница похожа на апгрейд монолитной архитектуры приложения до микросервисной архитектуры, а скорость итерации версии не быстрая и не сложная.

Рисунок 4: Жизненный цикл Java SE

2 Основы

2.1 module descriptor

Как упоминалось выше, ядром модуля являетсяmodule descriptor, соответствующий корневому каталогуmodule-info.classфайл, и этот файл класса создается корневым каталогом исходного кодаmodule-info.javaСкомпилируйте и сгенерируйте. Java этоmodule-info.javaБыл разработан специальный синтаксис, в том числеmodule,requires,exportsи другие ключевые слова (см.Рисунок 5).

Рисунок 5: Синтаксис module-info.java

Грамматическая интерпретация:

  • [open] module <module>: объявить модуль, имя модуля должно быть глобально уникальным и не может повторяться. плюсopenКлючевое слово указывает, что ко всем пакетам в модуле разрешен доступ через отражение Java, и использование тела объявления модуля больше не разрешено.opensутверждение.
  • requires [transitive] <module>: Объявить зависимости модулей. За один раз можно объявить только одну зависимость. Если она зависит от нескольких модулей, ее необходимо объявить несколько раз. плюсtransitiveКлючевые слова указывают на зависимости доставки, например, модуль A зависит от модуля B, модуль B передает зависимый модуль C, тогда модуль A будет автоматически зависеть от модуля C, подобно maven.
  • exports <package> [to <module1>[, <module2>...]]: экспортировать пакет внутри модуля (позволяет напрямуюimportUse), экспортируйте один пакет за раз, если вам нужно экспортировать несколько пакетов, вам нужно объявить это несколько раз. Если вам нужен направленный экспорт, вы можете использоватьtoКлючевые слова, за которыми следует список модулей (через запятую).
  • opens <package> [to <module>[, <module2>...]]: Откройте пакет в модуле (разрешив доступ через отражение Java), откройте один пакет за раз, если вам нужно открыть несколько пакетов, вам нужно объявить его несколько раз. Если вам нужно направленное открытие, вы можете использоватьtoКлючевые слова, за которыми следует список модулей (через запятую).
  • provides <interface | abstract class> with <class1>[, <class2> ...]: объявить службы Java SPI, предоставляемые модулем.Вы можете объявить несколько классов реализации служб одновременно (через запятую).
  • uses <interface | abstract class>: объявить службу Java SPI, от которой зависит модуль, и тогда код в модуле может пройтиServiceLoader.load(Class)Загружает сразу все классы реализации объявленной службы SPI.

2.2 -p и -m параметры

Java 9 представляет новый набор параметров для компиляции и запуска модулей, два наиболее важных из которых:-pи-m.-pПараметр указывает путь к модулю между множеством модулей с разделом «:» (Mac, Linux) или «;» (Windows) для применения.javacкоманда иjavaКоманды, использование и в Java 8-cpочень похожий.-mПараметр указывает основную функцию запускаемого модуля, а формат ввода:模块名/主函数所在的类名,Только дляjavaЗаказ. Основное использование двух параметров следующим образом:

  • javac -p <module_path> <source>

  • java -p <module_path> -m <module>/<main_class>

2.3 Демонстрационный пример

Чтобы помочь вам понятьmodule descriptorсинтаксис и новые параметры Java, я разработалПример проекта, который содержит 5 модулей:

  • Модуль mod1: основной модуль, показывающий два способа реализации классов с использованием сервисов.
  • Модуль mod2a: пакет экспортируется и открывается, и объявляются два класса реализации службы.
  • Модуль mod2b: объявляет недокументированный класс реализации службы.
  • Модуль mod3: определяет службы SPI (IEventListener) и объявляет недокументированный класс реализации службы.
  • Модуль mod4: Экспорт общедоступных классов моделей.

Рисунок-6: Пример проекта с 5 модулями

Сначала взгляните на основную функцию, в режиме 1 показано использование прямого экспорта и открытие двух модов2.IEventListenerКласс реализации, подход 2 показывает использование всехIEventListenerРеализация классов вне зависимости от того, экспортируются/открываются они или нет. По сравнению с режимом 1, режим 2 имеет еще две строки вывода, которые исходят от mod2b и mod3 соответственно.providesКласс реализации службы, предоставляемый ключевым словом.

public class EventCenter {

    public static void main(String[] args) throws ReflectiveOperationException {
        // 方式1:通过exports和opens
        System.out.println("Demo: Direct Mode");
        var listeners = new ArrayList<IEventListener>();
        // 使用导出类
        listeners.add(new EchoListener());
        // 使用开放类
        // compile error: listeners.add(new ReflectEchoListener());
        listeners.add((IEventListener<String>) Class.forName("mod2a.opens.ReflectEchoListener").getDeclaredConstructor().newInstance());
        var event = Events.newEvent();
        listeners.forEach(l -> l.onEvent(event));
        System.out.println();

        // 方式2:通过SPI
        System.out.println("Demo: SPI Mode");
        // 加载所有的IEventListener实现类,无视其导出/开放与否
        var listeners2 = ServiceLoader.load(IEventListener.class).stream().map(ServiceLoader.Provider::get).collect(Collectors.toList());
        // compile error: listeners.add(new InternalEchoListener());
        // compile error: listeners.add(new SpiEchoListener());
        var event2 = Events.newEvent();
        listeners2.forEach(l -> l.onEvent(event2));
    }
}

Код-1: mod1.EventCenter.java

Выполнить в командной строке./build_mods.shВывод получается следующим образом, результаты соответствуют ожиданиям.

Demo: Direct Mode
[echo] Event received: 68eb4671-c057-4bc2-9653-c31f5e3f72d2
[reflect echo] Event received: 68eb4671-c057-4bc2-9653-c31f5e3f72d2

Demo: SPI Mode
[spi echo] Event received: 678d239a-77ef-4b7f-b7aa-e76041fcdf47
[echo] Event received: 678d239a-77ef-4b7f-b7aa-e76041fcdf47
[reflect echo] Event received: 678d239a-77ef-4b7f-b7aa-e76041fcdf47
[internal echo] Event received: 678d239a-77ef-4b7f-b7aa-e76041fcdf47

Код-2: вывод результатов EventCenter

3 Расширенный

Увидев это, я считаю, что создание и запуск нового модульного приложения для вас больше не проблема, но вопрос в том, что насчет старого приложения Java 8? Не волнуйтесь, давайте сначала разберемся с двумя концепциями высокого уровня: безымянными модулями и автоматическими модулями.

Рисунок 7: Безымянные модули и автоматические модули

Будет ли немодулированный файл jar преобразован в безымянный модуль или автоматический модуль, зависит от пути, по которому появляется файл jar.Если это путь к классу, он будет преобразован в безымянный модуль, а если это путь к модулю, он будет Переключение на автоматический модуль. Обратите внимание, что автоматические модули также относятся к категории именованных модулей, и их имена автоматически выводятся системой модулей на основе имени файла jar.Например, имя автоматического модуля выводится из com.foo.bar-1.0.0. jar-файл com.foo .bar.Рисунок-7Перечислены различия в поведении между безымянными модулями и автоматическими модулями. Кроме того, между ними есть ключевое различие. Правило разделенного пакета применяется к автоматическим модулям, но недействительно для безымянных модулей, то есть можно экспортировать несколько безымянных модулей. Тот же пакет, но автоматические модули не допускаются.

Смысл существования безымянных модулей и автоматических модулей в том, что вне зависимости от того, является ли поступающий jar-файл легальным модулем (содержащимmodule descriptor), вся Java может обрабатываться унифицированным образом в модулях, что также является архитектурным принципом Java 9, совместимого со старыми версиями приложений. При запуске старой версии приложения все jar-файлы появляются в пути к классам, то есть они конвертируются в безымянные модули.Для безымянных модулей все пакеты экспортируются по умолчанию и зависят от всех модулей, поэтому приложение может работать нормально. Дальнейшее толкование см.Официальная Белая книгасоответствующие главы.

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

3.1 Стратегия «снизу вверх»

Первая стратегия, называемая стратегией «снизу вверх», основана на зависимостях пакетов jar (если зависимости сложные, вы можете использоватьjdepsинструмент для анализа), разделите пакет jar на модули снизу вверх по дереву зависимостей (добавьте файл описания легального модуля в корневую директорию исходного кода пакета jarmodule-info.java). Изначально все jar немодульные, все размещены в пути к классам (превращены в безымянные модули), а приложение запускается традиционным способом. Затем начните модульную сборку jar-пакета снизу вверх, и измененный jar-пакет перемещается в путь к модулям.В этот период приложение по-прежнему запускается традиционным способом. Наконец, после того, как все пакеты jar были модульными, приложение изменяется на-mспособ запуска, который также означает, что приложение было перенесено в настоящее приложение Java 9. Взяв пример проекта выше в качестве примера,

Рисунок 8: Стратегия модульности «снизу вверх»

  1. Предполагая, что изначально все пакеты jar не являются модульными, команда запуска приложения:

java -cp mod1.jar:mod2a.jar:mod2b.jar:mod3.jar:mod4.jar mod1.EventCenter

  1. Модульная переделка для mod3 и mod4. После завершения mod1, mod2a, mod2b по-прежнему являются обычными файлами jar, а новая рабочая команда:

java -cp mod1.jar:mod2a.jar:mod2b.jar -p mod3.jar:mod4.jar --add-modules mod3,mod4 mod1.EventCenter

По сравнению с командой на предыдущем шаге сначала mod3.jar и mod4.jar перемещаются из пути к классу в путь к модулю, что легко понять, поскольку эти два пакета jar были преобразованы в настоящие модули. Во-вторых, есть дополнительный параметр--add-modules mod3,mod4,Почему это? Это будет говорить о механизме обнаружения модулей модульной системы.

Либо во время компиляции или время выполнения, система модуля сначала должна определять один или несколько корневых модулей (корневой модуль), затем цикл начинается с корневого модуля в модуле модуля в соответствии с идентификацией модуля зависимостей все наблюдаемые (наблюдаемый модуль), Модуль можно наблюдать в файлах JAR PLUS PLUS CASSPATH, окончательной составляющей среды компиляции и среда выполнения. Тогда корневой модуль - как это определить? Для времени выполнения, если приложение через-mрежиме, то корневой модуль-mНазначенный основной модуль; если приложение запускается традиционным способом, корневой модуль полностьюjava.*Модули — это JRE (см.фигура 2).回到前面的例子,如果不加--add-modulesпараметр, то помимо JRE в среде исполнения будут сообщать только mod1.jar, mod2a.jar, mod2b.jar, без модулей mod3, mod4java.lang.ClassNotFoundExceptionаномальный. как Вы думаете,--add-modulesНазначение параметра — вручную указать дополнительные корневые модули, чтобы приложение могло нормально работать.

  1. Затем завершите модульное преобразование mod2a и mod2b.В это время выполняется команда:

java -cp mod1.jar -p mod2a.jar:mod2b.jar:mod3.jar:mod4.jar --add-modules mod2a,mod2b,mod4 mod1.EventCenter

Поскольку mod2a и mod2b зависят от mod3, mod3 не нужно добавлять в--add-modulesПараметры.

  1. Наконец, модульность mod1 завершена, и окончательная рабочая команда упрощается до:

java -p mod1.jar:mod2a.jar:mod2b.jar:mod3.jar:mod4.jar -m mod1/mod1.EventCenter

Обратите внимание, что в настоящее время приложение-mmode запускается, а mod1 указан как основной модуль (также корневой модуль), поэтому все остальные модули будут распознаны как наблюдаемые модули в соответствии с их зависимостями и добавлены в среду выполнения, и приложение может нормально работать.

3.2 Стратегия сверху вниз

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

Его основная идея состоит в том, чтобы проанализировать возможность модульного преобразования каждого пакета jar сверху вниз в соответствии с отношениями зависимости пакетов jar, начиная с основного приложения, по дереву зависимостей, и разделить пакеты jar на две категории, одну из которых можно модифицировано, класс не может быть преобразован. Для первой категории мы по-прежнему используем стратегию преобразования снизу вверх до тех пор, пока основное приложение не будет преобразовано, для второй категории нам нужно с самого начала поставить его в путь к модулю, то есть преобразовать его в автоматический модуль. Здесь мы поговорим о тонкостях проектирования автоматических модулей.Во-первых,автоматические модули будут экспортировать все пакеты, чтобы гарантировать, что первый тип jar-пакета может получить доступ к автоматическим модулям как обычно.Во-вторых,автоматические модули зависят от всех именованных модулей и позволяют доступ ко всем безымянным модулям Классы именованного модуля (это важно, потому что именованные модули, кроме автоматических модулей, не имеют доступа к классам безымянных модулей), чтобы сам автоматический модуль мог обращаться к другим классам, как обычно. После того, как основное приложение завершит модульное преобразование, метод запуска приложения можно изменить на-mСпособ.

Возьмем пример проекта в качестве примера, предполагая, что mod4 является сторонним jar-пакетом и не может быть модульным, тогда после окончательного преобразования, хотя команда запуска приложения такая же, как и раньше, она остается такой же, как и раньше.java -p mod1.jar:mod2a.jar:mod2b.jar:mod3.jar:mod4.jar -m mod1/mod1.EventCenter, но только mod1, mod2a, mod2b и mod3 являются реальными модулями, а mod4 не был изменен и преобразован в автоматический модуль системой модулей.

Рисунок 9: Стратегия модульности сверху вниз

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

4 дополнения

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

4.1 vs OSGi

Говоря о модульности, особенно в мире Java, она должна быть создателем OSGi, модульной системы. Пакеты в OSGi очень похожи на модули в модульной системе, все они существуют в виде jar-файлов, каждый пакет имеет свое имя, а также определяет зависимые пакеты, экспортируемые пакеты и опубликованные сервисы. Разница в том, что пакеты OSGi могут определять версии, а также концепцию жизненного цикла, включая установленные, разрешенные, удаленные, запущенные, активные, остановленные 6 состояний, все пакеты управляются контейнером OSGi, и в том же контейнере OSGi разрешается для одновременного запуска нескольких версий одного и того же пакета, даже каждый пакет имеет свой собственный независимый загрузчик классов. Вышеуказанные функции делают платформу OSGi очень тяжелой, и она становится все более и более маргинализованной, когда преобладают микросервисы.

4.2 vs Maven

Есть некоторое сходство между управлением зависимостями Maven и системой модулей.Все соответствующие модули артефакта в Maven существуют в виде файлов jar, которые имеют имена и могут объявлять транзитивные зависимости. Разница в том, что артефакт Maven поддерживает версии, но в нем отсутствует информация на уровне пакетов и концепция сервисов. Если Java родилась с модульной системой, то управление зависимостями Maven, скорее всего, будет разработано непосредственно на основе модульной системы.

4.3 vs ArchUnit

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

5 пасхальных яиц

Если вы видите здесь, поздравляю, вы завоевали 90% читателей. В знак признания вашего терпения, приветственное маленькое яйцо, дайте вам файл jar, как использовать самую быструю скорость распознавания, это не модуль? Как это определяется? Пытатьсяjar -d -f <jar_file>.

Вот и все введение в модульную систему Java, добро пожаловать в мойдоска объявленийДелитесь и делитесь со всеми. Увидимся в следующий раз.

6 Ссылка