Основы плагинов
Прежде чем запускать плагин на реальном сервере, разберитесь, как Nukkit-MOT распознаёт, загружает и включает ваш плагин.
Шаг 2 из 4: разберитесь с plugin.yml, главным классом и жизненным циклом плагина.
Предыдущий шаг: Первый плагин на Java
Следующий шаг: Запуск и отладка плагина
Как Nukkit-MOT загружает плагин
Когда вы помещаете jar в plugins/, процесс загрузки выглядит примерно так:
PluginManagerсканирует каталогplugins/в поисках файлов.jar.JavaPluginLoaderчитаетplugin.ymlиз корня jar.- Загрузчик ищет класс, объявленный в
main. - Создаётся экземпляр этого класса как
PluginBase. - Вызывается
onLoad(). - Позже плагин включается, и вызывается
onEnable(). - Когда сервер останавливается или плагин отключается, вызывается
onDisable().
Если обязательная зависимость из depend отсутствует, плагин не загрузится.
Минимальная структура проекта
Для обычного плагина на Java используйте такую структуру:
hello-world-plugin/
├─ pom.xml
└─ src/
└─ main/
├─ java/
│ └─ com/example/helloworld/HelloWorldPlugin.java
└─ resources/
└─ plugin.yml
Новичкам важнее всего два правила:
- Размещайте входной Java-класс в
src/main/java/ - Размещайте
plugin.ymlвsrc/main/resources/
Обязательные поля plugin.yml
Согласно текущей реализации PluginDescription, эти поля обязательны:
| Поле | Обязательно | Значение |
|---|---|---|
name | Да | Имя плагина. Оно также используется как имя папки данных плагина. |
main | Да | Полное имя входного класса, например com.example.helloworld.HelloWorldPlugin. |
version | Да | Строка версии плагина. Заключайте значения в кавычки, например "1.0.0". |
api | Да | Строка или список версий поддерживаемой Nukkit API. Список вида ["1.0.0"] — самый понятный стиль для новых плагинов. |
- Ваш главный класс должен наследоваться от
PluginBase - Ваш главный класс не должен находиться в пакете
cn.nukkit.* - После упаковки
plugin.ymlдолжен оказаться в корне jar
Распространённые необязательные поля
Эти поля не обязательны для минимального плагина HelloWorld, но вы будете использовать их часто:
| Поле | Назначение |
|---|---|
author / authors | Показывают, кто поддерживает плагин |
description | Краткое описание, отображаемое в метаданных |
website | Страница проекта, документация или репозиторий |
prefix | Префикс, используемый логгером плагина |
load | Момент загрузки: STARTUP или POSTWORLD |
depend | Жёсткие зависимости; их отсутствие блокирует загрузку плагина |
softdepend | Необязательные зависимости; без них плагин всё равно загрузится |
loadbefore | Указание, что ваш плагин должен загрузиться раньше другого плагина |
libraries | Необязательно. Внешние Maven-координаты (groupId:artifactId:version), которые Nukkit-MOT скачивает и изолирует для каждого плагина. Указывайте только те библиотеки, которые код вашего плагина действительно импортирует. См. Автоматическое разрешение Maven-библиотек. |
repositories | Необязательно. Дополнительные URL репозиториев, которые проверяются перед встроенными при разрешении libraries. Каждый URL должен заканчиваться на /. Нужны только для частных Nexus/Artifactory или хостов вне Maven Central; публичные координаты разрешаются и без них. |
commands | Метаданные команд, регистрируемые из plugin.yml |
permissions | Узлы прав (permissions), регистрируемые из plugin.yml |
По умолчанию load равен POSTWORLD. Используйте STARTUP только тогда, когда вашему плагину действительно нужно зарегистрировать что-то до завершения загрузки миров или других реестров.
Полный пример plugin.yml
Для базового плагина HelloWorld нужны только первые четыре поля, но более реалистичный файл часто выглядит так:
name: HelloWorldPlugin
main: com.example.helloworld.HelloWorldPlugin
version: "1.0.0"
api: ["1.0.0"]
author: YourName
description: A minimal example plugin for Nukkit-MOT
website: https://example.com
prefix: HelloWorld
load: POSTWORLD
softdepend:
- SomeOptionalPlugin
libraries:
- "com.squareup.okhttp3:okhttp:4.12.0"
- "org.xerial:sqlite-jdbc:3.45.1.0"
commands:
helloworld:
description: Send a hello message
usage: "/helloworld"
permission: helloworld.command
permissions:
helloworld.command:
description: Allows the player to use /helloworld
default: true
Вы можете удалить секции commands и permissions, пока не будете готовы добавить эти возможности.
Главный класс и жизненный цикл
Входной класс плагина обычно наследуется от PluginBase и переопределяет один или несколько методов жизненного цикла:
package com.example.helloworld;
import cn.nukkit.plugin.PluginBase;
public final class HelloWorldPlugin extends PluginBase {
@Override
public void onLoad() {
this.getLogger().info("Plugin is loading");
}
@Override
public void onEnable() {
this.getLogger().info("Plugin is enabled");
}
@Override
public void onDisable() {
this.getLogger().info("Plugin is disabled");
}
}
Используйте их так:
onLoad()— для ранней лёгкой инициализацииonEnable()— для обычной работы при запуске, например регистрации слушателей (listener), команд или задачonDisable()— для сохранения состояния и очистки ресурсов
Не помещайте логику запуска плагина в конструктор. Позвольте серверу управлять жизненным циклом.
Папка данных и ресурсы
Nukkit-MOT создаёт папку данных для вашего плагина на основе name плагина, обычно это:
plugins/HelloWorldPlugin/
Файлы из src/main/resources/ попадают в jar. Именно поэтому plugin.yml должен находиться там, а позже там же будет находиться и ваш стандартный config.yml.
Автоматическое разрешение Maven-библиотек (plugin.yml)
Вместо того чтобы вшивать (shading) большие сторонние библиотеки в jar вашего плагина и раздувать его размер, вы можете объявить их Maven-координаты прямо в plugin.yml. Nukkit-MOT скачивает каждую библиотеку в общую папку libraries/ внутри каталога данных сервера и подключает её к собственному ClassLoader вашего плагина во время загрузки.
libraries — необязательное поле. Каждая объявленная координата скачивается и разрешается при загрузке плагина (включая транзитивные зависимости), замедляя запуск и занимая место на диске независимо от того, использует ли ваш код эту библиотеку. Не копируйте весь список зависимостей из файла сборки — добавляйте координату только тогда, когда код вашего плагина импортирует эту библиотеку.
libraries:
- "com.squareup.okhttp3:okhttp:4.12.0"
- "org.xerial:sqlite-jdbc:3.45.1.0"
repositories:
- "https://maven.my-company.com/repository/public/"
- "https://jitpack.io"
Как работает разрешение
Для каждой записи groupId:artifactId:version сервер:
- Проверяет координату и отклоняет всё, что может выйти за пределы папки
libraries/(обход пути, точки в начале или конце, обратные слэши, управляющие символы). - Перебирает каждый URL из
repositoriesпо порядку, а затем встроенные резервные репозитории (Maven Central иrepo.lanink.cn). Побеждает первый найденный. - Скачивает
.jarвlibraries/<group path>/<artifact>/<version>/<artifact>-<version>.jar, сначала записывая файл.tmpи атомарно перемещая его на место. - Читает соответствующий
.pomи рекурсивно разрешает транзитивные зависимости по стратегииnearest-wins(побеждает первая встреченная версия для данногоgroupId:artifactId; конфликтные версии пропускаются, а не вытесняют уже найденные).
Уже скачанные артефакты повторно используются всеми плагинами, поэтому затраты происходят только один раз для каждой версии.
Изоляция ClassLoader
Каждый плагин получает собственный PluginClassLoader. Когда ваш плагин загружает класс:
- Сначала проверяются собственные URL плагина — сюда входят основной jar и каждый jar из
libraries. - Если класс там не найден, загрузчик обращается к глобальному поиску, поэтому существующий механизм
depend/softdependпродолжает работать.
Это означает, что версия, объявленная в libraries, имеет приоритет над одноимённым классом, вшитым в другой плагин. Это изоляция с приоритетом версии, а не контроль доступа — плагины по-прежнему могут видеть классы друг друга через глобальный резервный поиск.
Что поддерживается, а что нет
Поддерживается:
- Корневые
<dependencies>с литеральными версиями, областью видимостиcompileилиruntimeиoptional != true. - Транзитивное разрешение через такие поддерживаемые зависимости.
- Пользовательские репозитории, объявленные через
repositories.
Не поддерживается (парсер POM намеренно минималистичен):
- Наследование
parent,dependencyManagement, импорт BOM,exclusions,relocations. - Диапазоны версий, классификаторы и плейсхолдеры свойств
${...}в<version>POM. - Области видимости
provided,test,systemиimport— они отфильтровываются.
Если библиотеке требуется что-то из перечисленного, объявите дополнительные координаты явно в libraries или продолжайте вшивать эту зависимость в ваш jar.
Возможные сбои
- Некорректная координата (не ровно три части, разделённые
:, или содержащая недопустимые символы) вызываетLibraryLoadException, и плагин не загружается. - Если ни один из репозиториев не может отдать jar, плагин не загружается.
.pom, который не удаётся разобрать, не приводит к фатальному сбою: jar остаётся пригодным к использованию, отбрасываются лишь его транзитивные зависимости (в лог выводится предупреждение).- XML-парсер защищён от XXE (внешние сущности, DTD, схема) — вредоносные POM не могут обратиться к сети или читать локальные файлы через парсер.
Для популярных библиотек из Maven Central предпочитайте libraries вместо вшивания. Используйте repositories для частных URL Nexus/Artifactory — публичные координаты должны разрешаться и без него.
Прежде чем продолжить
- Самые частые причины сбоя загрузки — отсутствующий
plugin.ymlи неверное имя класса вmain - Если разрешение
librariesзавершается неудачей, лог сервера выводит проблемную координату и опробованные репозитории — проверьте URL и сеть, прежде чем переходить к вшиванию зависимостей - Если структура и метаданные верны, переходите к разделу Запуск и отладка плагина