如何定位Java中NotSerializableException的触发根源?
这种栈跟踪完全没指向自己代码的情况确实挺烦人的,我有几个实用的办法帮你快速定位到藏着BufferedImage的对象:
方法1:自定义ObjectOutputStream跟踪序列化流程
这是最直观的方式,通过自定义输出流打印每一个被序列化的对象信息,就能精准抓到触发异常的那个对象。
先写一个跟踪用的输出流类:
import java.io.IOException; import java.io.ObjectOutputStream; import java.io.OutputStream; public class TrackingObjectOutputStream extends ObjectOutputStream { public TrackingObjectOutputStream(OutputStream out) throws IOException { super(out); } @Override protected void writeObject(Object obj) throws IOException { // 打印当前序列化对象的类名和实例哈希值,方便区分不同实例 System.out.println("正在序列化: " + obj.getClass().getName() + " (实例ID: " + System.identityHashCode(obj) + ")"); super.writeObject(obj); } @Override protected void writeUnshared(Object obj) throws IOException { System.out.println("正在序列化(非共享): " + obj.getClass().getName()); super.writeUnshared(obj); } }
然后修改你的序列化工具方法,替换原来的ObjectOutputStream:
public static void serialize(Object o, String dirAndFileName) throws FileNotFoundException, IOException { FileOutputStream fileOut = new FileOutputStream(dirAndFileName); TrackingObjectOutputStream out = new TrackingObjectOutputStream(fileOut); out.writeObject(o); out.close(); }
运行测试后,控制台会打印所有被序列化的对象,最后一个打印出来的类就是触发异常的源头,你直接去检查这个类的字段就能找到BufferedImage了。
方法2:用反射直接排查目标对象
既然你已经定位到是passEffect序列化时出错,直接用反射递归遍历它的所有字段,一步找出藏着BufferedImage的地方。
写一个调试工具方法:
import java.awt.image.BufferedImage; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.HashSet; import java.util.Set; public class SerializationDebugger { // 用Set避免循环引用导致无限递归 private static final Set<Object> checkedObjects = new HashSet<>(); public static void findBufferedImage(Object obj) throws IllegalAccessException { if (obj == null || checkedObjects.contains(obj)) { return; } checkedObjects.add(obj); Class<?> clazz = obj.getClass(); // 遍历当前类的所有非静态、非transient字段 for (Field field : clazz.getDeclaredFields()) { if (Modifier.isStatic(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) { continue; } field.setAccessible(true); Object fieldValue = field.get(obj); // 找到BufferedImage字段直接输出信息 if (fieldValue instanceof BufferedImage) { System.out.println("找到BufferedImage字段: " + clazz.getName() + "." + field.getName()); return; } // 递归检查引用类型的字段(跳过JDK自带类,减少冗余) if (fieldValue != null && !fieldValue.getClass().isPrimitive() && !fieldValue.getClass().getName().startsWith("java.")) { findBufferedImage(fieldValue); } } // 递归检查父类字段 if (clazz.getSuperclass() != null && !clazz.getSuperclass().equals(Object.class)) { findBufferedImage(clazz.getSuperclass().cast(obj)); } } }
然后在测试代码里,序列化前先调用这个方法排查:
System.out.println("Saving test"); try { System.out.println("passeff"); // 先检查passEffect对象 SerializationDebugger.findBufferedImage(main.turnHandler.latestEvent.passEffect); cTools.Tools.serialize(main.turnHandler.latestEvent.passEffect, getSaveDir() + "t1"); // ... 剩下的代码 } catch (IOException | IllegalAccessException ex) { Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex); }
运行后控制台会直接输出包含BufferedImage的字段所在类和字段名,一步到位找到问题根源。
方法3:临时给BufferedImage加"假序列化"(应急调试用)
这个方法有点取巧,但能让栈跟踪显示更多上下文。你可以创建一个包装类让BufferedImage临时"可序列化",故意抛出带上下文的异常:
import java.awt.image.BufferedImage; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SerializableBufferedImage extends BufferedImage implements Serializable { public SerializableBufferedImage(BufferedImage img) { super(img.getWidth(), img.getHeight(), img.getType()); getGraphics().drawImage(img, 0, 0, null); } private void writeObject(ObjectOutputStream out) throws IOException { // 抛出带上下文的异常,指向当前对象的持有者 throw new IOException("尝试序列化BufferedImage,来自对象: " + this.getClass().getName()); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new IOException("反序列化不支持"); } }
临时把你怀疑的类里的BufferedImage字段替换成这个包装类,再运行序列化。这时抛出的异常会包含更多上下文信息,帮你定位到持有它的对象。注意这只是临时调试用,用完记得改回原代码。
推荐优先用方法1或方法2,这两个方法不需要修改业务代码,就能快速定位问题。
内容的提问来源于stack exchange,提问作者WVrock
相关产品推荐
相关产品推荐

