cómo probar en Java que una clase implementa Serializable correctamente (no solo es una instancia de Serializable)

Estoy implementando una clase para ser Serializable (por lo que es un objeto de valor para w / RMI). Pero necesito probarlo. ¿Hay alguna manera de hacer esto fácilmente?

aclaración : estoy implementando la clase, por lo que es trivial incluir Serializable en la definición de la clase. Necesito serializarlo / deserializarlo manualmente para ver si funciona.

Encontré esta pregunta de C # , ¿hay alguna respuesta similar para Java?

La manera fácil es verificar que el objeto sea una instancia de java.io.Serializable o java.io.Externalizable , pero eso realmente no prueba que el objeto realmente sea serializable.

La única forma de estar seguro es probarlo de verdad. La prueba más simple es algo así como:

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

y comprueba que no arroje una excepción.

Apache Commons Lang ofrece una versión más breve:

 SerializationUtils.serialize(myObject); 

y nuevamente, verifique la excepción.

Puedes ser aún más riguroso y verificar que se deserialice de nuevo en algo igual al original:

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

y así.

métodos de utilidad basados ​​en la respuesta de skaffman:

 private static  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 unpickle(byte[] b, Class cl) throws IOException, ClassNotFoundException { ByteArrayInputStream bais = new ByteArrayInputStream(b); ObjectInputStream ois = new ObjectInputStream(bais); Object o = ois.readObject(); return cl.cast(o); } 

Este código debería hacerlo …

 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); } } 

La respuesta corta es que puede encontrar algunos objetos candidatos y tratar de serializarlos utilizando el mecanismo que elija. La prueba aquí es que no se encuentran errores durante el marshalling / unmarshalling, y que el objeto resultante “rehidratado” es igual al original.

Alternativamente, si no tiene ningún objeto candidato, podría implementar una prueba basada en la reflexión que introspeccione los campos (no estáticos, no transitorios) de su clase para asegurarse de que también sean serializables. Hablando desde la experiencia, esto se vuelve sorprendentemente complejo sorprendentemente rápido, pero se puede hacer en una medida razonable.

La desventaja de este último enfoque es que si un campo es, por ejemplo, List , puede fallar la clase por no tener un campo estrictamente serializable, o simplemente asumir que se utilizará una implementación serializable de List. Ninguno de los dos es perfecto. (Tenga en cuenta que el último problema existe también para ejemplos: si cada ejemplo utilizado en la prueba utiliza listas serializables, no hay nada que impida que una versión no serializable sea utilizada por algún otro código en la práctica).

Puedes hacer la siguiente prueba:

  • Serialice el objeto al archivo y asegúrese de que no se lanza ninguna excepción.
  • Además, deserialice el objeto y compárelo con el objeto original.

Aquí hay un ejemplo para serializar y deserializar un objeto a un archivo:

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

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

Esto solo funciona para objetos completamente poblados, si requiere que cualquier objeto compuesto en su objeto de nivel superior también sea serializable, entonces no puede ser nulo para que esta prueba sea válida, ya que la serialización / deserialización omite los objetos nulos.

Intenté escribir una prueba unitaria (en Groovy usando Spock) que puede verificar que una interfaz dada para usar con RMI sea de hecho totalmente serializable: todos los parámetros, excepciones y posibles implementaciones de tipos definidos en los métodos.

Parece que hasta ahora me funciona, sin embargo, esto es un poco complicado de hacer y puede haber casos que esto no cubra, ¡así que utilícelo bajo su propio riesgo!

Tendrá que reemplazar el ejemplo de Notification interfaces, etc. con el suyo. El ejemplo incluye un campo que no se puede serializar como ilustración.

 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 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 findClasses(File directory, String packageName) throws ClassNotFoundException { List 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 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: * 
    *
  • primitives are implicitly serializable *
  • 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 *
* * @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
> 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> getMethodTypes(Class< ?> it) { Set> 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 allClasses = Package.packages.collectMany { Package p -> getClasses(p.name) as Collection } @CompileDynamic def "NotifierServer interface should only expose serializable or remotable types"() { given: Set types = getMethodTypes(NotifierServer) Set nonSerializableTypes = types.findAll { !isSerializableOrRemotable(it) } expect: nonSerializableTypes.empty } }