АЛГОРИТМ ЗАГРУЗКИ ПЛАГИНОВ, НЕ ИМЕЮЩИХ ЯВНЫХ ЗАВИСИМОСТЕЙ МЕЖДУ СОБОЙ

Математические структуры и моделирование 2021. №2 1(57). С. 101-107

УДК 004.42 DOI 10.24147/2222-8772.2021.1.101-107

АЛГОРИТМ ЗАГРУЗКИ ПЛАГИНОВ, НЕ ИМЕЮЩИХ ЯВНЫХ ЗАВИСИМОСТЕЙ МЕЖДУ СОБОЙ

Омский государственный университет им. Ф.М. Достоевского, Омск, Россия

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

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

Введение

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

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

Е.А. Тюменцев

преподаватель, e-mail: etyumentcev@gmail.com Д.Ю. Згуровец

студент, e-mail: daniilzgurovec@gmail.com

1. Реализация алгоритма

Предположим, что каждый плагин содержит класс, реализующий интерфейс

IPlugin

+ load

Рис. 1. Интерфейс для реализации плагинов

Процедура load() в интерфейсе IPlugin необходима для реализации метода загрузки, который при успешном выполнении приводит к правильной загрузке плагина, при ошибке в консоль выведется информация об исключении.

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

1. Каждый плагин необходимо наследовать от интерфейса IPlugin;

2. Подключаемая динамическая библиотека должна представлять собой архив с расширением «.jar»;

3. Подключаемая библиотека, помимо модулей, должна содержать в себе динамическую библиотеку plugin-api.jar, в которой находится интерфейс IPlugin для обеспечения возможности наследования от него плагинов;

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

5. При использовании сторонних плагинов необходимо создавать «обёртку» для них, так как иначе они не будут соответствовать критериям, перечисленным выше. Для решения этой задачи подходит паттерн Facade.

Работа алгоритма загрузки модулей происходит в несколько этапов.

1. Загрузка динамических библиотек в память.

Загрузка архивов с модулями, которая происходит в самом начале, при запуске системы. Архивы загружаются из заданной директории по маске «.jar», и содержащиеся в них модули помещаются в загрузчик классов. Затем архив распаковывается и его содержимое фильтруется следующим образом: происходит рекурсивная проверка на то, не является ли файл директорией или его расширение отличается от «.class», файлы внутри директорий тоже проходят проверку. Так как загрузчик классов работает с полными именами классов без расширения, «.class» необходимо отделить от имени. В проекте это реализовано простым удалением последних шести символов. Далее каждый отдельный модуль загружается по его имени.

2. Загрузка модулей из динамических библиотек в единый список.

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

3. Загрузка модулей из общего списка в память.

Следующий шаг — непосредственная загрузка самих плагинов. Для это на вход метода подаётся сформированный на прошлом шаге список, и для каждого элемента этого списка выполняется вызов функции load(), наследуемой от интерфейса IPlugin. Если модуль был успешно загружен, то алгоритм продолжается, иначе плагин добавляется во временный список. Далее алгоритм повторяется с временным списком. Так происходит до тем пор, пока все плагины не будут успешно загружены или итерация с временным списком не повторит предыдущую, это будет означать, что какие-то модули имеют циклическую зависимость. Ниже приведена блок-схема данной части алгоритма.

Рис. 2. Блок-схема алгоритма загрузки плагинов из готового списка

4. Отслеживание изменений в директории с модулями.

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

2. Тестирование

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

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

printtn

Рис. 3. Плагин с зависимостью

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

^i^jovfjCtoss.пдирJ loaded successfully")

Рис. 4. Плагин, от которого зависит другой модуль

Так как в языке Kotlin классы не могут иметь статических членов, а для реализации зависимости необходим экземпляр объекта, можно использовать структуру companion object, которая позволяет пометить объект ключевым словом companion и обращаться к его методам и полям без явного указания имени объекта, через имя содержащего его класса.

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

Алгоритм успешно прошёл тестирование.

3. Усовершенствование алгоритма

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

Один из способов решить эту проблему — запись порядка загрузки плагинов при успешном завершении работы алгоритма в data-файл. В случае с данным проектом для сохранения порядка загрузки был выбран формат json, для работы с которым использовалась библиотека Gson.

Так как загрузчик классов в Kotlin использует именно имена классов, то логично было бы помещать именно их в конфигурационный json-файл. Для получения имён классов был использован Java Reflection API. Загрузка модулей по имени из конфигурационного файла происходит по следующему сценарию: имена классов, полученные с помощью рефлексии, из общего списка проверяются на равенство с именами из конфигурационного файла, и в случае совпадения — у плагина вызывается метод load().

Часть алгоритма, в которой происходит загрузка модулей из списка, также видоизменяется. Теперь после удачного вызова метода load() необходимо добавлять имя класса загруженного плагина в отдельный список, чтобы после прохождения всеми модулями алгоритма записать получившийся список имён в json-файл. Блок-схема улучшенного алгоритма приведена ниже.

Рис. 5. Блок-схема алгоритма загрузки плагинов из готового списка

Заключение

Получен алгоритм динамической загрузки плагинов и описана его реализация на языке программирования Kotlin. Написаны тестовые модули для проверки работоспособности алгоритма. Весь исходный код доступен на GitHub[1]. Алгоритм выгрузки модулей из приложения вместе с зависимостями находился в стадии разработки на момент написания статьи.

Литература

1. Репозиторий проекта с алгоритмом и тестовыми модулями. URL: https:// github.com/DanyZy/Plugin-System (дата обращения: 12.02.2020).

ALGORITHM FOR LOADING PLUGINS THAT DO NOT HAVE EXPLICIT DEPENDENCIES AMONG THEMSELVES

E.A. Tumetcev

instructor, e-mail: etyumentcev@gmail.com D.Y. Zgurovets

student, e-mail: daniilzgurovec@gmail.com

Dostoevsky Omsk State University, Omsk, Russia

Abstract. This article describes a solution to the problem of explicit dependency injection in the modular architecture of a project by automating this process. The stages of development of a system for dynamic loading of plugins are analyzed in detail. Possible problems that may arise during the operation of the system, as well as selected methods for solving these problems are described. Examples of the operation of this system are given. At the time of writing, the project is under development, however, all the functionality described below has already been implemented and is suitable for use. A link to the project repository and test modules is in the list of references. This system is a library for the design of modular architecture, the scope of which is limited to the list given in the article below.

Keywords: module, plugin, architecture, Kotlin, dependencies, optimization.

References

1. Repozitorii proekta s algoritmom i testovymi modulyami. URL: https://github. com/DanyZy/Plugin-System

Дата поступления в редакцию: 12.10.2020