Статьи

Почему Maven не может сгенерировать декларацию вашего модуля

Чтобы объявить модули для системы модулей Java 9, нам нужно создать файл module-info.java — так называемое объявление модуля . Среди прочего, он объявляет зависимости от других модулей. Если в проекте используется инструмент сборки, например, Maven, он уже управляет зависимостями. Синхронизация двух файлов наверняка кажется избыточной и подверженной ошибкам, поэтому возникает вопрос: «Не может ли Maven сгенерировать для меня файл информации модуля?» К сожалению, ответ «Нет», и вот почему.

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

Мотивация

Таким образом, все задаются вопросом, может ли Maven генерировать объявление модуля? Причина очевидна: если я добавлю зависимость в POM, это, скорее всего, тоже требование. Или наоборот: если я добавлю требование в объявление, возможно, в POM должна быть добавлена ​​соответствующая зависимость. Похоже, что-то, что может быть автоматизировано.

Давайте начнем с попытки перейти от требования к зависимости: это невозможно из-за недостатка информации. Имя модуля не может быть преобразовано в координату Maven, отсутствует информация, такая как groupId и artifactId. А также: какую версию выбрать? Объявление модуля не интересует, какая версия артефакта находится на пути, а только то, что оно доступно.

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

Разделение проблем

Когда мы говорим на эту тему, в игре участвуют три объекта: POM Maven, файл информации о модульной системе и плагины Maven, которые участвуют в создании путей к классам и модулям. Три вместе позволяют работать с модулями, но все они имеют свои обязанности.

POM Зависимости

Задача зависимости состоит в том, чтобы (1) иметь уникальную ссылку на файл на основе координаты и (2) указать, когда его использовать. Когда использовать его, часть контролируется scope и представляет собой любую комбинацию compile , test и runtime .

Координата представляет собой комбинацию, по крайней мере, groupId , artifactId , version и расширения файла, которая является производной от type . При желании может быть добавлен classifier . На основании этой информации можно ссылаться на файл в локальном хранилище. Это будет выглядеть следующим образом, где каждая точка в groupId заменяется косой чертой:

 ${localRepo}/${groupId}/${artifactId}/${version}/ ${artifactId}-${version}[-${classifier}].${ext} 

Как видите, вы можете ссылаться на любой файл: текстовый файл, изображение, исполняемый файл. В контексте зависимости это не имеет значения. Чтобы добавить к этому, зависимость не имеет никакого представления о содержании файла. Например, в случае JAR: был ли он построен для Java 8 или Java 1.4, что может иметь большое значение в случае, если ваш проект должен быть совместим с довольно старой версией Java.

Декларация модуля

Файл декларации модуля состоит из пяти деклараций:

требует
Модули, которые должны быть доступны для компиляции или запуска этого приложения.
экспорт
Пакеты, типы которых видны нескольким или всем модулям.
Открытие
Пакет (ы), типы которых [доступны через отражение] (https://www.sitepoint.com/reflection-vs-encapsulation-in-the-java-module-system/) для нескольких или всех модулей.
использования
Сервисный интерфейс (ы), который может обнаружить этот модуль.
обеспечивает
Реализация (и) предусмотрена для определенного интерфейса сервиса.

Из этих деклараций пункты require тесно связаны с зависимостями проекта Maven. Если JDK / JRE вместе с файлами зависимостей, предоставленными Maven, не покрывают эти требования, проект просто не будет компилироваться или запускаться. Считайте это правилом качества, которому нужно подчиняться.

Плагины Maven

Каждый плагин (или фактически плагин-цель) может указать область разрешения для зависимостей и получить доступ к этим файлам. Например, цель компиляции подключаемого модуля maven-compiler утверждает, что он использует разрешение зависимостей во время компиляции. Именно этот плагин создает правильные аргументы для компилятора Java на основе файлов зависимостей, предоставленных Maven, и конфигурации плагина.

Пути классов и пути модулей

Вплоть до Java 8 это было довольно просто: все JAR-файлы должны быть добавлены в путь классов. Но с Jigsaw JAR могут оказаться либо на пути к модулю, либо на пути к классам. Все зависит от того, требуется ли JAR другим модулем или нет. Важно понимать, что мы должны сделать это правильно 100% времени, потому что, если артефакт окажется на неправильном пути, система модулей отклонит конфигурацию, и компиляция или запуск завершатся неудачно.

Давайте предположим, что мы написали декларацию модуля для нашего проекта. Где мы должны указать, является ли JAR обязательным модулем или нет? Другими словами, он принадлежит пути к классу или к модулю? Одним из очевидных мест является зависимость в POM. Однако есть несколько причин, почему это не сработает.

Не беспокойство

Прежде всего, это не касается зависимости. Зависимость — это ссылка на файл и когда его использовать (время компиляции? Время выполнения?), А не как .

Нет места в POM

Затем из всех элементов зависимости есть только один, который может использоваться для управления более детальным использованием JAR: scope . Однако модель POM modelVersion 4.0.0 имеет строгий набор областей действия. Хотя POM заполнен инструкциями по сборке для Maven, после установки или развертывания он также является метафайлом с информацией о зависимостях для других инструментов сборки и IDE, поэтому новые области действия могут запутать или сломать такие продукты.

По той же причине введение нового XML-элемента для зависимостей было бы проблематичным; это будет конфликтовать с XSD, и другие инструменты еще не готовы к этому. Так что в объявлении зависимостей POM нет места для модульной информации, и в краткосрочной перспективе не следует ожидать нового определения POM только для Jigsaw.

Так как насчет его настройки с помощью maven-compiler-plugin?

 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <!-- <version>xyz</version> --> <configuration> <!-- example --> <modulePath> <!-- by dependency? --> <dependency>com.foo.bar:library</dependency> <!-- by module? --> <module>library</module> </modulePath> </configuration> </plugin> 

Но как насчет транзитивных зависимостей, в данном случае зависимостей библиотеки ? Эти зависимости также должны быть разделены на оба пути. Вам нужно настроить это?

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

Чтение дескриптора модуля

Так что, если POM — это не тот путь, откуда мы можем получить информацию? Лучше сосредоточиться на файлах внутри JAR как есть, например, module-info.class … (объявление скомпилированного модуля, называемое дескриптором модуля ).

Таким образом, ключевой файл выглядит как файл информации module-info как он точно определяет требования. Когда такие инструменты, как ASM, могут прочитать дескриптор модуля, maven-compiler-plugin может извлечь эту информацию и решить для каждой зависимости, соответствует ли он требуемому модулю. Для нашего проекта должен быть module-info.java чтобы Maven знал, где разместить наши зависимости. Для этого его нужно проанализировать перед компиляцией, что уже возможно с QDox .

Таким образом, со всеми module-info.class внутри JAR и module-info.java нашего проекта можно решить, к module-info.java из них принадлежит каждый JAR; либо путь к классу, либо путь к модулю. Это делает объявления модуля и дескрипторы компонентом для успешной сборки. Но остается вопрос, не может ли это быть и результатом ?

Maven не может создавать объявления модулей

Модуль декларации генератора

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

Для требований в src/main/java/module-info.java мы можем продвинуться довольно далеко. Мы могли бы сказать, что все зависимости с областями, которые compile или provided являются требованиями, конечно, нет, но для runtime (что означает только во время выполнения ) это зависит. Но есть и требования, которые не сопоставлены с JAR. Вместо этого они ссылаются на модуль java или jdk (например, java.sql , java.xml или jdk.packager ). Они должны быть настроены для генератора или код должен быть проанализирован заранее.

Требование также может иметь дополнительные модификаторы. С помощью предложения requires static оператора вы можете указать, что модуль является обязательным при компиляции, но необязательным при выполнении. В Maven вы бы отметили такую ​​зависимость как optional . С помощью модификатора transitive процесса вы указываете, что проектам, использующим этот модуль, не нужно добавлять переходный модуль в свой собственный файл объявления модуля. Таким образом, transitive оказывает никакого влияния на этот проект, он только помогает другим проектам, использующим этот как зависимость. Такая информация может быть предоставлена ​​только в качестве конфигурации.

Даже если определение POM будет переработано для поддержки такого рода метаданных зависимостей, чтобы преобразовать их в требования, содержащиеся в файле объявлений модуля, существует гораздо больше объявлений, которые генерировать гораздо сложнее. Он уже начинается с самого модуля: как он называется, открыт он или нет?

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

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

Самое близкое, что мы могли бы получить — это шаблон, подобный следующему. Давайте использовать Velocity и назовем шаблон module-info.java.vm .

 open module MN { #set ( $requiredModules = ... ) ## these must somehow be available in the context ## requires {RequiresModifier} ModuleName ; #set( $transitiveModules = ["org.acme.M1", "org.acme.M2"] ) #foreach( $requiredModule in ${requiredModules} ) #if( $transitiveModules.contains( $requiredModule.name ) ) transitive #end #if( $requiredModule.optional ) static #end ${requiredModule.name} ; #end requires java.sql; requires java.xml; requires jdk.packager; ## exports PackageName [to ModuleName {, ModuleName}] ; exports com.acme.product; exports com.acme.service; ## opens PackageName [to ModuleName {, ModuleName}] ; ## uses TypeName ; ## provides TypeName with TypeName {, TypeName} ; } 

Преобразование этого в module-info.java требует, вероятно, отдельного шаблонного плагина maven, потому что это выходит за рамки задачи компиляции maven-compiler-plugin и копирования ресурсов с помощью дополнительной задачи фильтрации maven-resource-plugin. Это зависит от разработчика, если такой шаблон с некоторым синтаксическим синтаксисом лучше читать и поддерживать по сравнению с простым старым (новым) файлом объявления модуля.

Некоторые, возможно, слышали о jdeps, инструменте, доступном начиная с JDK8, который может анализировать зависимости. Он также имеет возможность генерировать файл объявления модуля, но не так, как мы этого хотим. Jdeps использует только скомпилированные классы, поэтому он должен использоваться после фазы compile . Эта функция была введена для разработчиков, чтобы иметь для начала module-info.java , но после того, как он сгенерирован, он должен настроить и поддерживать его.

Вывод

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

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

Я более надеюсь, что IDE решат это за вас. Например, если вы добавляете зависимость в POM, вы можете получить возможность добавить ее как требование к объявлению модуля. Или что они предоставляют мастера, показывающего все зависимости и пакеты из вашего проекта, давая вам возможность, что добавить в файл объявления модуля.

Однако эта часть истории заканчивается, но я здесь, чтобы сказать вам, что в настоящее время Maven не будет писать декларацию вашего модуля для вас.