Создание дополнений

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

Дополнения легко создаются с помощью шаблона Single Module Add-on мастера создания проекта Studio. Полученный проект будет содержать два модуля исходного кода: функциональный модуль и стартовый модуль.

По умолчанию функциональный модуль будет содержать зависимости от подсистем доступа к данным и пользовательского интерфейса Jmix. Всю функциональность следует создавать в этом модуле.

Стартовый модуль содержит автоконфигурацию Spring Boot для вашего дополнения. Чтобы использовать дополнение в приложении или другом дополнении, вам нужно добавить зависимость от артефакта стартового модуля в файл build.gradle целевого проекта, например:

dependencies {
    implementation 'com.company.sample:sample-starter:0.0.1'

Функциональный модуль будет включаться транзитивно через стартер.

Иерархия дополнений

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

Например, когда приложение запускается, оно может выполнять Liquibase changelogs для создания или обновления базы данных. Файлы changelog из разных подсистем должны выполняться в определенном порядке, потому что если на сущности дополнения А ссылается дополнение B, то таблицы дополнения А должны создаваться перед таблицами дополнения B.

Если какая-то базовая функция поддерживает переопределение, иерархия обеспечивает предсказуемый конечный результат переопределения. Например, если дополнения A, B и C определяют значение одного и того же свойства приложения, а C зависит от B, которое зависит от A, то вы можете быть уверены, что приложение, использующее все эти дополнения, получит значение из дополнения С.

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

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

@JmixModule

В проекте дополнения функциональный модуль содержит основной класс конфигурации с аннотациями @Configuration,@ComponentScan и @JmixModule. Последняя аннотация указывает на то, что модуль должен быть включен в иерархию модулей Jmix.

Атрибуты @JmixModule:

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

    Обычно базовые пакеты дополнений уникальны, поэтому атрибут id не нужен. Но если ваше дополнение содержит тесты, класс тестовой конфигурации находится в том же базовом пакете, что и основная конфигурация. Поэтому вы должны указать уникальный идентификатор для тестового модуля, например, добавив суффикс .test к имени пакета:

    package modularity.sample.base;
    // ...
    @JmixModule(id = "modularity.sample.base.test",
            dependsOn = BaseConfiguration.class)
    public class BaseTestConfiguration {
  • dependsOn - объявляет модули, от которых зависит этот модуль. Этот атрибут должен содержать массив классов конфигураций модулей.

    Например, если дополнение зависит от подсистем Core, Data Access и UI, оно содержит следующие зависимости в build.gradle:

    dependencies {
        implementation 'io.jmix.core:jmix-core-starter'
        implementation 'io.jmix.data:jmix-eclipselink-starter'
        implementation 'io.jmix.ui:jmix-ui-starter'

    Затем его аннотация @JmixModule должна объявлять зависимости от классов EclipselinkConfiguration и UiConfiguration:

    @JmixModule(dependsOn = {EclipselinkConfiguration.class, UiConfiguration.class})
    public class BaseConfiguration {

    Зависимость от подсистемы Core привносится транзитивно, это можно проверить, если посмотреть определение класса EclipselinkConfiguration или UiConfiguration.

При запуске приложение Jmix выводит в лог сообщение INFO от логгера io.jmix.core.JmixModulesProcessor с результатом топологической сортировки иерархии модулей. Например:

Using Jmix modules: [io.jmix.core, io.jmix.security, io.jmix.ui,
    io.jmix.securityui, io.jmix.data, io.jmix.eclipselink,
    io.jmix.securitydata, com.company.users, com.company.customers,
    com.company.products, io.jmix.localfs, io.jmix.uidata,
    com.company.sales]
Убедитесь, что модуль приложения стоит последним в списке после всех дополнений. Если это не так, скорее всего, зависимости между вашими дополнениями определены неправильно, поэтому проверьте содержимое их @JmixModule(dependsOn).

Свойства модуля

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

src/main/resources/modularity/sample/base/module.properties
jmix.ui.menu-config = modularity/sample/base/menu.xml

Затем укажите путь к файлу в аннотации @PropertySource основного класса конфигурации:

package modularity.sample.base;
// ...
@PropertySource(name = "modularity.sample.base",
        value = "classpath:/modularity/sample/base/module.properties")
public class BaseConfiguration {
В аннотации @PropertySource должен быть задан атрибут name с идентификатором модуля (который обычно равен имени базового пакета).

Вы могли заметить, что в приведенном выше примере дополнение определяет jmix.ui.menu-config, который обычно также определяется в приложении. Так почему же значение приложения просто не переопределяет значение надстройки? На самом деле так и есть: если вы получите значение jmix.ui.menu-config из Spring Environment в приложении, вы получите только значение, определенное в приложении. Но используя бин – компонент JmixModules, вы можете получить значения определенного свойства из всех модулей, используемых в приложении. Например:

@Autowired
Environment environment;
@Autowired
JmixModules jmixModules;

void checkProperties() {
    String envProp = environment.getProperty("jmix.ui.menu-config");
    // modularity/sample/ext/menu.xml

    List<String> moduleProps = jmixModules.getPropertyValues("jmix.ui.menu-config");
    // [io/jmix/ui/menu.xml, modularity/sample/base/menu.xml, modularity/sample/ext/menu.xml]
}

Этот подход широко используется во фреймворке для агрегирования конфигураций, определенных в дополнениях, таких как меню UI, общие фетч-планы, аутентифицированные/анонимные URL-адреса REST и другие.