A good way to find unserializable fields in Java

后端 未结 2 1735
野性不改
野性不改 2021-02-10 14:46

I have a quite complex Java object to serialize (which worked fine a couple of weeks ago). After having implemented a lot in the meantime, the serialization now fails throwing t

相关标签:
2条回答
  • 2021-02-10 15:13

    Okay, inspired by the idea of Ben Lawry who, however, did not present an actual implementation, I have also programmed a class crawler which finds "unserializable fields", i.e., non-static, non-transient fields that are either not implementing the Serializable interface themselves or contain fields that are non-static, non-transient and do not implement the Serializable interface.

    You can add several classes to be tested in the list in the main method (see comment). The output is a list of fields from these classes or any referenced classes that are not serializable by the above definition (which, however, is not exactly equal to the actually serializable fields, but captures a superset of the latter); for each field which itself does implement the Serializable interface, a list of fields referenced in the type of that field is given which cause the field being not serializable (if the list is empty the field itself does not implement the Serializable interface).

    Here is the code, hope it helps others, too:

    import java.io.Serializable;
    import java.lang.reflect.Field;
    import java.lang.reflect.Modifier;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.LinkedList;
    
    import javax.swing.JComponent;
    
    public class Crawler {
        public static boolean crawlRecursively(Field field, HashSet<Class<?>> alreadyCrawled, HashMap<Field, HashSet<String>> badFields) {
            if (alreadyCrawled.contains(field.getType())) {
                return !badFields.keySet().contains(field);
            }
    
            alreadyCrawled.add(field.getType());
    
            if (Modifier.isStatic(field.getModifiers())
                    || Modifier.isTransient(field.getModifiers())
                    || field.getType().isPrimitive()) {
                return true;
            } else if (Serializable.class.isAssignableFrom(field.getType())) {
                boolean allGood = true;
    
                for (Field f : field.getType().getDeclaredFields()) {
                    boolean isGood = crawlRecursively(f, alreadyCrawled, badFields);
                    if (!isGood) {
                        if (!badFields.containsKey(field)) {
                            badFields.put(field, new HashSet<>());
                        }
                        badFields.get(field).add(f.getType().getSimpleName() + " " + f.getName());
                        allGood = false;
                    }
                }
    
                return allGood;
            } else {
                if (!badFields.containsKey(field)) {
                    badFields.put(field, new HashSet<>());
                }
    
                return false;
            }
        }
    
        public static HashMap<Field, HashSet<String>> initiateCrawling(Collection<Class<?>> roots) {
            HashMap<Field, HashSet<String>> badFields = new HashMap<>();
    
            for (Class<?> root : roots) {
                for (Field f : root.getDeclaredFields()) {
                    crawlRecursively(f, new HashSet<>(), badFields);
                }
            }
    
            return badFields;
        }
    
        public static void main(String[] args) {
            LinkedList<Class<?>> roots = new LinkedList<>();
            roots.add(JComponent.class); // ADD YOUR CLASSES HERE.
            HashMap<Field, HashSet<String>> badFields = initiateCrawling(roots);
    
            if (badFields.keySet().size() == 0) {
                System.out.println("All fields are serializable (not having checked the given class(es) themselves).");
            } else {
                System.out.println("The following fields are not serializable in the class tree(s) given by " + roots + ":");
            }
    
            for (Field field : badFields.keySet()) {
                System.out.println("<UnSer> "
                        + field.getType().getSimpleName() + " " 
                        + field.getName() + " (" 
                        + field.getDeclaringClass().getName() + ") => " + badFields.get(field));
            }
        }
    }
    

    Example output for class JComponent:

    The following fields are not serializable in the class tree(s) given by [class javax.swing.JComponent]:
    <UnSer> Border border (javax.swing.JComponent) => []
    <UnSer> ComponentInputMap windowInputMap (javax.swing.JComponent) => [JComponent component]
    <UnSer> VetoableChangeSupport vetoableChangeSupport (javax.swing.JComponent) => [VetoableChangeListenerMap map, Object source]
    <UnSer> SingleSelectionModel selectionModel (javax.swing.JPopupMenu) => []
    <UnSer> JPopupMenu popupMenu (javax.swing.JComponent) => [SingleSelectionModel selectionModel]
    <UnSer> Object source (java.beans.VetoableChangeSupport) => []
    <UnSer> VetoableChangeListenerMap map (java.beans.VetoableChangeSupport) => []
    <UnSer> JComponent component (javax.swing.ComponentInputMap) => [VetoableChangeSupport vetoableChangeSupport, JPopupMenu popupMenu, Border border, InputVerifier inputVerifier]
    <UnSer> InputVerifier inputVerifier (javax.swing.JComponent) => []
    
    0 讨论(0)
  • 2021-02-10 15:28

    The library class can be assumed to be correctly serializable, i.e. to declare all non-serializable fields as transient, and therefore shouldn't be the source of the error.

    If your code is using ContextedRuntimeExceptions, they are an exception to the above rule because the objects they reference may not be serializable, and this is noted in the Javadoc at the methods addContextValue() and setContextValue(). If context values are the problem, it would explain why the suspect classes got a reference to a Graphics2D object without having such a field.

    I have had limited success replicating NotSerializableException for SunGraphics2D without leaving an obvious field with "Graphics" in its name laying around, which could be due to testing on a simpler class structure. I found that in addition to contexted exceptions, collections were a good suspect but only if they used raw types. Collections could also cause intermittent failures if they were only sometimes populated with Graphics.

    Depending on your application, Ben Lawry's automated solution to a similar issue may be easier to implement than manually hunting for the above. If that path works, it will likely be more thorough as well. Can you get a stack trace?

    0 讨论(0)
提交回复
热议问题