Spigot 1.20.4雕像插件:自定义StatueAction序列化问题求助
问题背景
我正在开发基于Spigot 1.20.4(Java)的雕像插件,用于创建NPC雕像。目前创建雕像时,可通过StatueAction定义NPC被右键点击时的行为,代码示例:
Statue.StatueBuilder builder = new Statue.StatueBuilder(entityType) .setName("Test") .setOnClick(connectionPlayer -> System.out.println("TEST " + new Random().nextInt())); Statue statue = builder.build();
其中setOnClick接收的StatueAction定义为:
public interface StatueAction { void perform(ConnectionPlayer player); }
需要将雕像数据保存到文件(YML/JSON等),但无法序列化该自定义行为。尝试YML和JSON序列化后,onClick字段输出为空对象{},当前序列化代码如下:
序列化尝试代码:
StatueAction<ConnectionPlayer> r = (connectionPlayer) -> System.out.println("Hello"); Statue.StatueBuilder builder = new Statue.StatueBuilder(entityType).setName("Test").setOnClick(r); Statue statue = builder.build(); statue.spawn(player.getLocation()); statue.createDefaultAttribs(); System.out.println(statue.serialize());
序列化输出:
{"name":"Test","entityType":"ARMOR_STAND","entityUUID":"6c2a484c-3168-4fb8-a3ee-8c2d93bddf1d","textDisplaysID":[],"onClick":{}}
Statue的序列化方法:
public static class StatueSerializer implements JsonSerializer<Statue> { @Override public JsonElement serialize(Statue statue, Type type, JsonSerializationContext jsonSerializationContext) { JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("name", statue.name); jsonObject.addProperty("entityType", statue.entityType.name()); jsonObject.addProperty("entityUUID", statue.getEntityUUID().toString()); jsonObject.add("textDisplaysID", jsonSerializationContext.serialize(statue.getTextDisplaysID())); // Serialize the CustomAction using its own serialization logic jsonObject.add("onClick", jsonSerializationContext.serialize(statue.getOnClick())); return jsonObject; } } public String serialize() { Gson gson = new GsonBuilder() .registerTypeAdapter(Statue.class, new Statue.StatueSerializer()) .create(); return gson.toJson(this); }
解决方案
直接序列化Lambda不可行:Lambda依赖JVM动态生成的内部类,类名不稳定,且反序列化需要完全一致的类加载环境,Spigot插件的类加载器机制会导致重启后反序列化失败,同时序列化内容不可读、不可维护,不适合插件配置场景。以下是推荐的替代方案:
方案1:动作标识符+注册表(推荐)
核心思路是给每个可复用的动作分配唯一标识符,序列化时仅存储标识符,反序列化时通过注册表匹配对应的动作实例。
- 创建动作注册表
public class StatueActionRegistry { private static final Map<String, StatueAction> ACTION_MAP = new HashMap<>(); // 注册动作,插件加载时执行 public static void register(String actionId, StatueAction action) { ACTION_MAP.put(actionId, action); } // 根据ID获取动作实例 public static StatueAction getAction(String actionId) { return ACTION_MAP.get(actionId); } }
- 修改Statue类存储动作ID
public class Statue { private String name; private EntityType entityType; private UUID entityUUID; private List<String> textDisplaysID; private String onClickActionId; // 存储动作ID而非动作实例 // 更新Builder方法 public static class StatueBuilder { private Statue statue = new Statue(); public StatueBuilder setName(String name) { statue.name = name; return this; } public StatueBuilder setEntityType(EntityType type) { statue.entityType = type; return this; } public StatueBuilder setOnClickActionId(String actionId) { statue.onClickActionId = actionId; return this; } public Statue build() { return statue; } } // 获取动作实例 public StatueAction getOnClickAction() { return StatueActionRegistry.getAction(onClickActionId); } // 更新序列化逻辑 public static class StatueSerializer implements JsonSerializer<Statue> { @Override public JsonElement serialize(Statue statue, Type type, JsonSerializationContext ctx) { JsonObject json = new JsonObject(); json.addProperty("name", statue.name); json.addProperty("entityType", statue.entityType.name()); json.addProperty("entityUUID", statue.entityUUID.toString()); json.add("textDisplaysID", ctx.serialize(statue.textDisplaysID)); json.addProperty("onClickActionId", statue.onClickActionId); return json; } } // 添加反序列化逻辑 public static class StatueDeserializer implements JsonDeserializer<Statue> { @Override public Statue deserialize(JsonElement json, Type type, JsonDeserializationContext ctx) throws JsonParseException { JsonObject obj = json.getAsJsonObject(); Statue statue = new Statue(); statue.name = obj.get("name").getAsString(); statue.entityType = EntityType.valueOf(obj.get("entityType").getAsString()); statue.entityUUID = UUID.fromString(obj.get("entityUUID").getAsString()); statue.textDisplaysID = ctx.deserialize(obj.get("textDisplaysID"), new TypeToken<List<String>>(){}.getType()); statue.onClickActionId = obj.get("onClickActionId").getAsString(); return statue; } } // 更新序列化方法 public String serialize() { Gson gson = new GsonBuilder() .registerTypeAdapter(Statue.class, new StatueSerializer()) .registerTypeAdapter(Statue.class, new StatueDeserializer()) .create(); return gson.toJson(this); } }
- 注册预定义动作
在插件主类的onEnable方法中注册所有需要的动作:
@Override public void onEnable() { StatueActionRegistry.register("print_random", player -> { System.out.println("TEST " + new Random().nextInt()); }); StatueActionRegistry.register("print_hello", player -> { System.out.println("Hello"); }); }
- 创建雕像时使用动作ID
Statue.StatueBuilder builder = new Statue.StatueBuilder(EntityType.ARMOR_STAND) .setName("Test") .setOnClickActionId("print_random"); Statue statue = builder.build();
方案2:预定义动作枚举(适合固定动作场景)
如果动作数量少且逻辑固定,可以用枚举类封装所有动作,序列化时存储枚举名称。
- 定义动作枚举
public enum PredefinedStatueAction implements StatueAction { PRINT_RANDOM { @Override public void perform(ConnectionPlayer player) { System.out.println("TEST " + new Random().nextInt()); } }, PRINT_HELLO { @Override public void perform(ConnectionPlayer player) { System.out.println("Hello"); } }; }
- 修改Statue类存储枚举实例
public class Statue { private PredefinedStatueAction onClickAction; // 更新Builder方法 public StatueBuilder setOnClickAction(PredefinedStatueAction action) { this.onClickAction = action; return this; } // 序列化时存储枚举名称 public static class StatueSerializer implements JsonSerializer<Statue> { @Override public JsonElement serialize(Statue statue, Type type, JsonSerializationContext ctx) { JsonObject json = new JsonObject(); // ... 其他字段序列化 json.addProperty("onClickAction", statue.onClickAction.name()); return json; } } // 反序列化时解析枚举 public static class StatueDeserializer implements JsonDeserializer<Statue> { @Override public Statue deserialize(JsonElement json, Type type, JsonDeserializationContext ctx) throws JsonParseException { JsonObject obj = json.getAsJsonObject(); Statue statue = new Statue(); // ... 其他字段解析 statue.onClickAction = PredefinedStatueAction.valueOf(obj.get("onClickAction").getAsString()); return statue; } } }
方案3:脚本引擎(适合动态自定义动作)
如果需要支持用户通过配置文件编写动态动作,可以使用Java内置的脚本引擎(如Nashorn),序列化时存储脚本内容,反序列化时编译执行。
- 修改Statue类存储脚本
public class Statue { private String onClickScript; // 更新Builder方法 public StatueBuilder setOnClickScript(String script) { this.onClickScript = script; return this; } // 执行脚本的方法 public void performOnClick(ConnectionPlayer player) { ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); try { engine.put("player", player); // 将玩家对象传入脚本 engine.eval(onClickScript); } catch (ScriptException e) { e.printStackTrace(); } } // 序列化时存储脚本字符串 public static class StatueSerializer implements JsonSerializer<Statue> { @Override public JsonElement serialize(Statue statue, Type type, JsonSerializationContext ctx) { JsonObject json = new JsonObject(); // ... 其他字段序列化 json.addProperty("onClickScript", statue.onClickScript); return json; } } }
- 创建雕像时传入脚本
Statue.StatueBuilder builder = new Statue.StatueBuilder(EntityType.ARMOR_STAND) .setName("Test") .setOnClickScript("print('Hello ' + player.getName());"); Statue statue = builder.build();
注意:此方法存在安全风险,需限制脚本权限,仅适合可控环境使用。
内容的提问来源于stack exchange,提问作者A_Brave_Panda
相关产品推荐
相关产品推荐

