深色模式
扩展 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.SealAttributeExtensionsettings.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重新编译并做隔离启动测试。