跳到正文

扩展 JAR 与 ServiceLoader

扩展 JAR 用于在 SealAttributes 注册表冻结前增加属性、资源、公式和行为。它不是普通 Bukkit 插件:入口只拿到 DefinitionRegistry,没有 Bukkit 生命周期,也不能在运行期为玩家发布来源。

加载规则

  • 目录固定为 plugins/SealAttributes/extensions/*.jar
  • 最多 64 个,按文件名稳定排序;
  • 每个 JAR 必须恰好提供一个 SealAttributeExtension
  • providerId() 是该 JAR 拥有的唯一命名空间;
  • apiBaseline() 主版本必须相同,所需次版本不能高于服务端;
  • 缺 service、多个 provider、基线不兼容、命名空间越权、重复 ID、注册异常都会让整个主插件启动失败
  • 每个扩展使用独立 URLClassLoader,不支持扩展间依赖;
  • 不热加载/卸载,替换必须正常停服;
  • 扩展是可信任意 JVM 代码,不是安全沙箱。

最小 Java 扩展项目

目录:

text
example-attribute-pack/
├─ settings.gradle.kts
├─ build.gradle.kts
└─ src/main/
   ├─ java/example/attributes/ExampleAttributeExtension.java
   └─ resources/META-INF/services/
      └─ cn.sealplugins.attributes.api.extension.SealAttributeExtension

settings.gradle.kts

kotlin
rootProject.name = "example-attribute-pack"

build.gradle.kts

kotlin
plugins {
    java
}

group = "example"
version = "1.0.0"

repositories {
    mavenCentral()
    // 把维护者交付的 sealattributes-api:1.0.9 发布到你的私有仓库,
    // 或使用经过版本锁定的本地 Maven 仓库;不要依赖未经确认的公网坐标源。
}

dependencies {
    compileOnly("cn.seal:sealattributes-api:1.0.9")
}

java {
    toolchain.languageVersion.set(JavaLanguageVersion.of(21))
}

ExampleAttributeExtension.java

java
package example.attributes;

import cn.sealplugins.attributes.api.SealAttributesApiMetadata;
import cn.sealplugins.attributes.api.definition.AttributeDefinition;
import cn.sealplugins.attributes.api.definition.AttributeId;
import cn.sealplugins.attributes.api.definition.AttributeType;
import cn.sealplugins.attributes.api.definition.DefinitionRegistry;
import cn.sealplugins.attributes.api.definition.RegistrationResult;
import cn.sealplugins.attributes.api.extension.SealAttributeExtension;

public final class ExampleAttributeExtension implements SealAttributeExtension {
    @Override public String providerId() { return "examplepack"; }

    @Override public String apiBaseline() {
        return SealAttributesApiMetadata.BASELINE;
    }

    @Override public void register(DefinitionRegistry registry) {
        AttributeDefinition luck = AttributeDefinition.dataOnly(
            AttributeId.of("examplepack", "luck"),
            "幸运",
            AttributeType.RATING,
            0.0
        );
        RegistrationResult result = registry.register(luck);
        if (!result.accepted()) {
            throw new IllegalStateException(
                "examplepack:luck registration failed: " + result.getStatus()
            );
        }
    }
}

service 文件内容必须只有实现类全名:

text
example.attributes.ExampleAttributeExtension

构建:

powershell
.\gradlew.bat clean build

把生成的 JAR 放入 plugins/SealAttributes/extensions/ 后完整启动。预期 attributes.yml 自动追加 examplepack:luck 的缺失配置块,/sa inspect <玩家> examplepack:luck 可查询默认值 0。

依赖打包

  • SealAttributes API 必须 compileOnly,不能 shade;
  • JDK 类不用打包;
  • 其他第三方运行库需自行 shade/relocate,因为扩展类加载器不共享扩展间依赖;
  • Kotlin 编写的扩展若依赖 Kotlin runtime,应把所需 runtime 安全打包,不能假设主插件导出内部依赖。

失败示例

java
AttributeId.of("other", "luck")

当 provider 是 examplepack 时会得到 NAMESPACE_MISMATCH

忽略 RegistrationResult 也不安全;注册窗口已冻结会返回 WINDOW_CLOSED。应检查稳定状态码,不能解析日志文本。

扩展与普通 Paper 插件怎么选

需求选择
注册新属性/资源/公式/战斗行为扩展 JAR
监听 Bukkit 事件、读玩家职业、发布实时来源普通 Paper 插件 + 公开 API
两者都有一个自包含扩展注册定义,加一个普通插件发布业务来源;用稳定命名空间协作

扩展不能通过反射访问 Core/Paper 内部实现。只依赖公开 API baseline,升级时对目标 API JAR重新编译并做隔离启动测试。

Minecraft 服务端插件使用与开发文档