wie man in Java testet, dass eine Klasse Serializable korrekt implementiert (nicht nur eine Instanz von Serializable)

77

Ich implementiere eine Klasse, die serialisierbar ist (es ist also ein Wertobjekt zur Verwendung mit RMI). Aber ich muss es testen. Gibt es eine Möglichkeit, dies einfach zu tun?

Klarstellung : Ich implementiere die Klasse, daher ist es trivial, Serializable in die Klassendefinition aufzunehmen. Ich muss es manuell serialisieren / deserialisieren, um zu sehen, ob es funktioniert.

Ich habe diese C # -Frage gefunden . Gibt es eine ähnliche Antwort für Java?

Jason S.
quelle
Versuchen Sie zu testen, ob das Objekt ordnungsgemäß serialisiert und deserialisiert wird?
Vivin Paliath

Antworten:

128

Der einfache Weg besteht darin, zu überprüfen, ob das Objekt eine Instanz von java.io.Serializableoder ist java.io.Externalizable, aber das beweist nicht wirklich, dass das Objekt wirklich serialisierbar ist.

Der einzige Weg, um sicher zu sein, ist es, es wirklich zu versuchen. Der einfachste Test ist so etwas wie:

new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(myObject);

und überprüfen Sie, ob es keine Ausnahme auslöst.

Apache Commons Lang bietet eine eher kurze Version:

SerializationUtils.serialize(myObject);

Überprüfen Sie erneut die Ausnahme.

Sie können noch strenger sein und überprüfen, ob es wieder zu etwas deserialisiert wird, das dem Original entspricht:

Serializable original = ...
Serializable copy = SerializationUtils.clone(original);
assertEquals(original, copy);

und so weiter.

Skaffman
quelle
Nur für den Fall, dass jemand neugierig ist oder die Apache Commons-Bibliotheken nicht einbeziehen möchte, verfügt Spring auch über SerializationUtils mit den Methoden serializeund deserialize. Siehe SerializationUtils
Peter Kirby
Ihre Antwort hat mir geholfen, aber deserialize()und clone()zurück Object, nicht Serializable.
NeplatnyUdaj
In der alten commons-lang v2 ist das wahr, aber nicht seit v3 - commons.apache.org/proper/commons-lang/javadocs/api-3.4/org/…
skaffman
4
Auch das ist nicht kinderleicht. Wenn ein Objekt unserialisierbare Felder hat, diese aber sind null, ist es serialisierbar. Initialisieren Sie sie und es wird nicht sein.
Wu-Lee
28

Utility-Methoden basierend auf der Antwort von Skaffman:

private static <T extends Serializable> byte[] pickle(T obj) 
       throws IOException 
{
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(obj);
    oos.close();
    return baos.toByteArray();
}

private static <T extends Serializable> T unpickle(byte[] b, Class<T> cl)
       throws IOException, ClassNotFoundException 
{
    ByteArrayInputStream bais = new ByteArrayInputStream(b);
    ObjectInputStream ois = new ObjectInputStream(bais);
    Object o = ois.readObject();
    return cl.cast(o);
}
Jason S.
quelle
1
++ danke dir und skaffman! Ich suchte nach etwas Ähnlichem, um es einem Test-Harness hinzuzufügen, um Entwickler daran zu hindern, nicht serialisierbaren Code zu schreiben und unsere Apps zu beenden, sobald sie unseren Cluster-Server erreichen. Ein Lebensretter! Danke Leute!
Chris Aldrich
Mehr über den Begriff "Beizen" hier: docs.python.org/2/library/pickle.html
Spanne
Einfach. Elegant. Vielen Dank dafür ... Ich gehe davon aus, dass das Kopieren und Einfügen in Ordnung ist.
Scottb
3

Die kurze Antwort lautet: Sie können einige Kandidatenobjekte erstellen und tatsächlich versuchen, sie mithilfe des Mechanismus Ihrer Wahl zu serialisieren. Der Test hier ist, dass beim Marshalling / Unmarshalling keine Fehler auftreten und dass das resultierende "rehydratisierte" Objekt dem Original entspricht.

Wenn Sie keine Kandidatenobjekte haben, können Sie alternativ einen reflexionsbasierten Test implementieren, der die (nicht statischen, nicht transienten) Felder Ihrer Klasse überprüft, um sicherzustellen, dass auch sie serialisierbar sind. Erfahrungsgemäß wird dies überraschend schnell überraschend komplex, kann aber in angemessenem Umfang durchgeführt werden.

Der Nachteil dieses letzteren Ansatzes besteht darin, dass Sie, wenn ein Feld z. B. ist List<String>, entweder die Klasse nicht bestehen können, weil sie kein streng serialisierbares Feld hat, oder einfach davon ausgehen können, dass eine serialisierbare Implementierung von List verwendet wird. Weder ist perfekt. (Allerdings besteht das letztere Problem auch für Beispiele. Wenn in jedem im Test verwendeten Beispiel serialisierbare Listen verwendet werden, kann nichts verhindern, dass eine nicht serialisierbare Version in der Praxis von einem anderen Code verwendet wird.)

Andrzej Doyle
quelle
3

Dies funktioniert nur für vollständig ausgefüllte Objekte. Wenn Sie benötigen, dass alle in Ihrem Objekt der obersten Ebene zusammengesetzten Objekte auch serialisierbar sind, können sie nicht null sein, damit dieser Test gültig ist, da durch die Serialisierung / Deserialisierung die Nullobjekte übersprungen werden

ouster
quelle
3

Dieser Code sollte es tun ...

import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;

public class Main
{
    public static void main(String[] args)
    {
        System.out.println(isSerializable("Hello"));
        System.out.println(isSerializable(new Main()));
    }

    public static boolean isSerializable(final Object o)
    {
        final boolean retVal;

        if(implementsInterface(o))
        {
            retVal = attemptToSerialize(o);
        }
        else
        {
            retVal = false;
        }

        return (retVal);
    }

    private static boolean implementsInterface(final Object o)
    {
        final boolean retVal;

        retVal = ((o instanceof Serializable) || (o instanceof Externalizable));

        return (retVal);
    }

    private static boolean attemptToSerialize(final Object o)
    {
        final OutputStream sink;
        ObjectOutputStream stream;

        stream = null;

        try
        {
            sink   = new ByteArrayOutputStream();
            stream = new ObjectOutputStream(sink);
            stream.writeObject(o);
            // could also re-serilalize at this point too
        }
        catch(final IOException ex)
        {
            return (false);
        }
        finally
        {
            if(stream != null)
            {
                try
                {
                    stream.close();
                }
                catch(final IOException ex)
                {
                    // should not be able to happen
                }
            }
        }

        return (true);
    }
}
TofuBeer
quelle
instanceof Externalizableimpliziert instanceof Serializable. Sie müssen nicht beide testen.
Marquis von Lorne
2

Sie können folgenden Test durchführen:

  • Serialisieren Sie das Objekt in die Datei und stellen Sie sicher, dass keine Ausnahme ausgelöst wird.
  • Deserialisieren Sie außerdem das Objekt zurück und vergleichen Sie es mit dem Originalobjekt.

Hier ist ein Beispiel für die Serialisierung und Deserialisierung von Objekten in Dateien:

http://www.rgagnon.com/javadetails/java-0075.html

http://www.javapractices.com/topic/TopicAction.do?Id=57

YoK
quelle
0

Ich habe versucht, einen Komponententest (in Groovy mit Spock) zu schreiben, mit dem überprüft werden kann, ob eine bestimmte Schnittstelle zur Verwendung mit RMI tatsächlich vollständig serialisierbar ist - alle Parameter, Ausnahmen und möglichen Implementierungen der in den Methoden definierten Typen.

Bisher scheint es für mich zu funktionieren, dies ist jedoch etwas umständlich und es kann Fälle geben, die nicht abgedeckt sind. Verwenden Sie es daher auf eigenes Risiko!

Sie müssen die Beispielschnittstellen Notificationusw. durch Ihre eigenen ersetzen . Das Beispiel enthält zur Veranschaulichung ein unserialisierbares Feld.

package example

import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import spock.lang.Specification

import java.lang.reflect.*
import java.rmi.Remote
import java.rmi.RemoteException

/** This checks that the a remoting API NotifierServer is safe
 *
 * It attempts to flush out any parameter classes which are
 * not Serializable. This isn't checked at compile time!
 *
 */
@CompileStatic
class RemotableInterfaceTest extends Specification {
    static class NotificationException extends RuntimeException {
        Object unserializable
    }

    static interface Notification {
        String getMessage()

        Date getDate()
    }

    static interface Notifier extends Remote {
        void accept(Notification notification) throws RemoteException, NotificationException
    }


    static interface NotifierServer extends Remote {
        void subscribe(Notification notifier) throws RemoteException
        void notify(Notification message) throws RemoteException
    }

    // From https://www.javaworld.com/article/2077477/learn-java/java-tip-113--identify-subclasses-at-runtime.html
    /**
     * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
     *
     * @param packageName The base package
     * @return The classes
     * @throws ClassNotFoundException
     * @throws IOException
     */
    static Class[] getClasses(String packageName)
            throws ClassNotFoundException, IOException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader()
        assert classLoader != null
        String path = packageName.replace('.', '/')
        Enumeration resources = classLoader.getResources(path)
        List<File> dirs = new ArrayList()
        while (resources.hasMoreElements()) {
            URL resource = resources.nextElement()
            dirs.add(new File(resource.getFile()))
        }
        ArrayList classes = new ArrayList()
        for (File directory : dirs) {
            classes.addAll(findClasses(directory, packageName))
        }
        return classes.toArray(new Class[classes.size()])
    }

    /**
     * Recursive method used to find all classes in a given directory and subdirs.
     *
     * @param directory   The base directory
     * @param packageName The package name for classes found inside the base directory
     * @return The classes
     * @throws ClassNotFoundException
     */
    static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
        List<Class> classes = new ArrayList()
        if (!directory.exists()) {
            return classes
        }
        File[] files = directory.listFiles()
        for (File file : files) {
            if (file.isDirectory()) {
                //assert !file.getName().contains(".");
                classes.addAll(findClasses(file, packageName + "." + file.getName()))
            } else if (file.getName().endsWith(".class")) {
                classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)))
            }
        }
        return classes
    }

    /** Finds all known subclasses of a class */
    @CompileDynamic
    static List<Class> getSubclasses(Class type) {
        allClasses
            .findAll { Class it ->
                !Modifier.isAbstract(it.modifiers) &&
                it != type &&
                type.isAssignableFrom(it)
            }
    }

    /** Checks if a type is nominally serializable or remotable.
     *
     * Notes:
     * <ul>
     * <li> primitives are implicitly serializable
     * <li> interfaces are serializable or remotable by themselves, but we
     * assume that since #getSerializedTypes checks derived types of interfaces,
     * we can safely assume that all implementations will be checked
     *</ul>
     *
     * @param it
     * @return
     */
    static boolean isSerializableOrRemotable(Class<?> it) {
        return it.primitive || it.interface || Serializable.isAssignableFrom(it) || Remote.isAssignableFrom(it)
    }

    /** Recursively finds all (new) types associated with a given type 
     * which need to be serialized because they are fields, parameterized
     * types, implementations, etc. */
    static void getSerializedTypes(final Set<Class<?>> types, Type... it) {
        for(Type type in it) {
            println "type: $type.typeName"

            if (type instanceof GenericArrayType) {
                type = ((GenericArrayType)type).genericComponentType
            }

            if (type instanceof ParameterizedType) {
                ParameterizedType ptype = (ParameterizedType)type
                getSerializedTypes(types, ptype.actualTypeArguments)
                break
            }


            if (type instanceof Class) {
                Class ctype = (Class)type

                if (ctype == Object)
                    break

                if (types.contains(type))
                    break

                types << ctype
                for (Field field : ctype.declaredFields) {
                    println "${ctype.simpleName}.${field.name}: ${field.type.simpleName}"
                    if (Modifier.isVolatile(field.modifiers) ||
                        Modifier.isTransient(field.modifiers) ||
                        Modifier.isStatic(field.modifiers))
                        continue

                    Class<?> fieldType = field.type
                    if (fieldType.array)
                        fieldType = fieldType.componentType

                    if (types.contains(fieldType))
                        continue

                    types << fieldType
                    if (!fieldType.primitive)
                        getSerializedTypes(types, fieldType)
                }

                if (ctype.genericSuperclass) {
                    getSerializedTypes(types, ctype.genericSuperclass)
                }

                getSubclasses(ctype).each { Class c -> getSerializedTypes(types, c) }

                break
            }
        }
    }

    /** Recursively checks a type's methods for related classes which
     * need to be serializable if the type is remoted */
    static Set<Class<?>> getMethodTypes(Class<?> it) {
        Set<Class<?>> types = []
        for(Method method: it.methods) {
            println "method: ${it.simpleName}.$method.name"
            getSerializedTypes(types, method.genericParameterTypes)
            getSerializedTypes(types, method.genericReturnType)
            getSerializedTypes(types, method.genericExceptionTypes)
        }
        return types
    }

    /** All the known defined classes */
    static List<Class> allClasses = Package.packages.collectMany { Package p -> getClasses(p.name) as Collection<Class> }


    @CompileDynamic
    def "NotifierServer interface should only expose serializable or remotable types"() {
        given:
        Set<Class> types = getMethodTypes(NotifierServer)

        Set<Class> nonSerializableTypes = types.findAll { !isSerializableOrRemotable(it) }

        expect:
        nonSerializableTypes.empty
    }

}
Wu-Lee
quelle