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

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

Прежде чем запускать плагин на реальном сервере, разберитесь, как 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 и сеть, прежде чем переходить к вшиванию зависимостей
  • Если структура и метаданные верны, переходите к разделу Запуск и отладка плагина