*~~今天是儿童节，而我在写代码~~* ### 写这东西的原因 本来写一个简简单单的插件以为会很快，结果写到将ItemStack序列化并保存为文件这里的时候，我才发现原来Bukkit中的ItemStack这东西并没有实现Serializable这个接口。看一下源代码，是这么写的： ```java public class ItemStack implements Cloneable, ConfigurationSerializable ``` 完蛋，它实现的是ConfigurationSerializable接口，也就是说如果要序列化ItemStack的时候，只能是序列化成YAML的一组元素，这就变得阴间起来了，且不说它序列化之后原本物品中包含的NBT标签（包括非原版的NBT或者是自定义的标签）能不能被Bukkit正常识别，而且操作也很阴间。 ### 第一步 我现在想要把包含ItemStack的一个类叫UIPart的东西实例化(这里我需要用UIPart存储三个ItemStack，然后在别的地方渲染UI的时候用)，首先得让这东西实现Java的序列化接口： ```java public class UIPart implements Serializable ``` 因为是内部类，所以完整代码如下: ```java public static class UIPart implements Serializable { private final String smelt; private final String fuel; private final String result; public UIPart(String smelt, String fuel, String result) { this.smelt = smelt; this.fuel = fuel; this.result = result; } public ItemStack getSmelt() { return NBTUtil.deserializeItemStack(smelt); } public ItemStack getFuel() { return NBTUtil.deserializeItemStack(fuel); } public ItemStack getResult() { return NBTUtil.deserializeItemStack(result); } } ``` 其中，NBTUtil.deserializeItemStack(String)方法会在后文中提到。 ### 第二步 如果要将ItemStack序列化，需要用到NMS包与OBC包，为什么呢？只因为Bukkit提供的API并没有给你操作它NBT标签数据的方式，因此只能使用原版类也就是NMS的方式来操作。而Bukkit与NMS交互的中间人就是OBC（是包名org.bukkit.craftbukkit的简称），所以要通过NMS来操作Bukkit的ItemStack类，需要用到OBC来将其转换成CraftItemStack进而转换为NMS的ItemStack类。1.17/1.18以后的版本NMS不再以上述方式获取，因为新版本用了mojang官方的映射表，因此1.18以后如需调用NMS则需要额外的步骤，反射：使用新的包名，如net.minecraft.item，普通方式：需要重映射原版端后才能调用，不然看到的类就是混淆后的东西比如a.a.B。 那么，我们现在需要通过反射的方式来获取NMS与OBC包，因为在不同版本中，这些包被单独存放在了不同的位置，如net.minecraft.server.v1_7_R4以及org.bukkit.craftbukkit.v1_7_R4，v1_7_R4是版本号，对应1.7.10版本。 获取NMS类： ```java private static Class getNMSClass(String name) { String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; try { return Class.forName("net.minecraft.server." + version + "." + name); } catch (ClassNotFoundException ex) { ex.printStackTrace(); return null; } } ``` 获取OBC类： ```java private static Class getOBClass(String name) { String version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; try { return Class.forName("org.bukkit.craftbukkit." + version + "." + name); } catch (ClassNotFoundException ex) { ex.printStackTrace(); return null; } } ``` ### 第三步 在以上步骤完成以后，我们就能将ItemStack进行序列化以及反序列化了。 序列化ItemStack: ```java public static String serializeItemStack(ItemStack item) { if (item == null) { return null; } ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); DataOutputStream dataOutput = new DataOutputStream(outputStream); try { Class nbtTagCompoundClass = getNMSClass("NBTTagCompound"); Constructor nbtTagCompoundConstructor = nbtTagCompoundClass.getConstructor(); Object nbtTagCompound = nbtTagCompoundConstructor.newInstance(); Object nmsItemStack = getOBClass("inventory.CraftItemStack").getMethod("asNMSCopy", ItemStack.class).invoke(null, item); getNMSClass("ItemStack").getMethod("save", nbtTagCompoundClass).invoke(nmsItemStack, nbtTagCompound); getNMSClass("NBTCompressedStreamTools").getMethod("a", nbtTagCompoundClass, DataOutput.class).invoke(null, nbtTagCompound, dataOutput); } catch (SecurityException | NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); } return new BigInteger(1, outputStream.toByteArray()).toString(32); } ``` 反序列化ItemStack： ```java public static ItemStack deserializeItemStack(String data){ if (data == null) { return null; } ByteArrayInputStream inputStream = new ByteArrayInputStream(new BigInteger(data, 32).toByteArray()); DataInputStream dataInputStream = new DataInputStream(inputStream); ItemStack itemStack = null; try { Class nbtTagCompoundClass = getNMSClass("NBTTagCompound"); Class nmsItemStackClass = getNMSClass("ItemStack"); Object nbtTagCompound = getNMSClass("NBTCompressedStreamTools").getMethod("a", DataInputStream.class).invoke(null, dataInputStream); Object craftItemStack = nmsItemStackClass.getMethod("createStack", nbtTagCompoundClass).invoke(null, nbtTagCompound); itemStack = (ItemStack) getOBClass("inventory.CraftItemStack").getMethod("asBukkitCopy", nmsItemStackClass).invoke(null, craftItemStack); } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { e.printStackTrace(); } return itemStack; } ``` ### 第四步 将UIPart类序列化并保存为文件，以便下次加载。 UIPart类内的ItemStack会序列化成String存储，下次反序列化UIPart获取ItemStack时再反序列化字符串。 文件名称，约定俗成为ser后缀。 其中smelt、fuel、result均为ItemStack。 ```java UIPart part = new UIPart(NBTUtil.serializeItemStack(smelt), NBTUtil.serializeItemStack(fuel), NBTUtil.serializeItemStack(result)); FileOutputStream output = new FileOutputStream("pair.ser"); ObjectOutputStream objOutput = new ObjectOutputStream(output); objOutput.writeObject(part); objOutput.close(); output.close(); ``` 将UIPart反序列化： ```java FileInputStream input = new FileInputStream("pair.ser"); ObjectInputStream objInput = new ObjectInputStream(input); UIPart part = (UIPart) objInput.readObject(); objInput.close(); input.close(); ```