Перейти к основному содержимому

Основы плагинов

Прежде чем запускать плагин на реальном сервере, разберитесь, как Nukkit-MOT распознаёт, загружает и включает ваш плагин.

Где вы находитесь

Шаг 2 из 4: разберитесь с plugin.yml, главным классом и жизненным циклом плагина.

Предыдущий шаг: Первый плагин на Java

Следующий шаг: Запуск и отладка плагина

Как Nukkit-MOT загружает плагин​

Когда вы помещаете jar в plugins/, процесс загрузки выглядит примерно так:

  1. PluginManager сканирует каталог plugins/ в поисках файлов .jar.
  2. JavaPluginLoader читает plugin.yml из корня jar.
  3. Загрузчик ищет класс, объявленный в main.
  4. Создаётся экземпляр этого класса как PluginBase.
  5. Вызывается onLoad().
  6. Позже плагин включается, и вызывается onEnable().
  7. Когда сервер останавливается или плагин отключается, вызывается 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 нужны только первые четыре поля, но более реалистичный файл часто выглядит так:

plugin.yml
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 и переопределяет один или несколько методов жизненного цикла:

HelloWorldPlugin.java
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 — необязательное поле. Каждая объявленная координата скачивается и разрешается при загрузке плагина (включая транзитивные зависимости), замедляя запуск и занимая место на диске независимо от того, использует ли ваш код эту библиотеку. Не копируйте весь список зависимостей из файла сборки — добавляйте координату только тогда, когда код вашего плагина импортирует эту библиотеку.

plugin.yml — declarative 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 сервер:

  1. Проверяет координату и отклоняет всё, что может выйти за пределы папки libraries/ (обход пути, точки в начале или конце, обратные слэши, управляющие символы).
  2. Перебирает каждый URL из repositories по порядку, а затем встроенные резервные репозитории (Maven Central и repo.lanink.cn). Побеждает первый найденный.
  3. Скачивает .jar в libraries/<group path>/<artifact>/<version>/<artifact>-<version>.jar, сначала записывая файл .tmp и атомарно перемещая его на место.
  4. Читает соответствующий .pom и рекурсивно разрешает транзитивные зависимости по стратегии nearest-wins (побеждает первая встреченная версия для данного groupId:artifactId; конфликтные версии пропускаются, а не вытесняют уже найденные).

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

Изоляция ClassLoader​

Каждый плагин получает собственный PluginClassLoader. Когда ваш плагин загружает класс:

  1. Сначала проверяются собственные URL плагина — сюда входят основной jar и каждый jar из libraries.
  2. Если класс там не найден, загрузчик обращается к глобальному поиску, поэтому существующий механизм 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 и сеть, прежде чем переходить к вшиванию зависимостей
  • Если структура и метаданные верны, переходите к разделу Запуск и отладка плагина