J'implémente une classe qui doit être Serializable (c'est donc un objet de valeur à utiliser avec RMI). Mais j'ai besoin de le tester. Y at-il un moyen de faire cela facilement?
clarification: J'implémente la classe, il est donc trivial de coller Serializable dans la définition de classe. J'ai besoin de sérialiser/désérialiser manuellement pour voir si cela fonctionne.
J'ai trouvé cette question C # , existe-t-il une réponse similaire pour Java?
Le moyen le plus simple est de vérifier que l'objet est une instance de Java.io.Serializable
ou Java.io.Externalizable
, mais cela ne prouve pas vraiment que l'objet est vraiment sérialisable.
La seule façon d’en être sûr est de l’essayer réellement. Le test le plus simple est quelque chose comme:
new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(myObject);
et vérifiez qu'il ne jette pas une exception.
Apache Commons Lang fournit une version un peu plus brève:
SerializationUtils.serialize(myObject);
et encore, vérifiez l'exception.
Vous pouvez être encore plus rigoureux et vérifier qu'il se désérialise en quelque chose de égal à l'original:
Serializable original = ...
Serializable copy = SerializationUtils.clone(original);
assertEquals(original, copy);
etc.
méthodes utilitaires basées sur la réponse de 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);
}
Ce code devrait le faire ...
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);
}
}
Cela ne fonctionne que pour les objets entièrement remplis. Si vous souhaitez que les objets composés dans votre objet de niveau supérieur soient également sérialisables, ils ne peuvent pas être null pour que ce test soit valide, car la sérialisation/désérialisation ignore les objets nuls
Vous pouvez faire le test suivant:
Voici un exemple de sérialisation et de désérialisation d'objet en fichier:
La réponse courte est que vous pouvez proposer des objets candidats et essayer de les sérialiser en utilisant le mécanisme de votre choix. Le test ici est qu'aucune erreur n'est rencontrée pendant le marshalling/unmarshalling, et que l'objet "réhydraté" résultant est égal à l'original.
Sinon, si vous n'avez aucun objet candidat, vous pouvez implémenter un test basé sur la réflexion qui introspecte les champs (non statiques, non transitoires) de votre classe pour vous assurer qu'ils sont également sérialisables. Par expérience, cela devient étonnamment complexe, d'une rapidité surprenante, mais cela peut être fait dans une mesure raisonnable.
L’inconvénient de cette dernière approche est que si un champ est, par exemple, List<String>
, vous pouvez soit échouer la classe pour ne pas avoir un champ strictement sérialisable, soit simplement supposer qu'une implémentation sérialisable de List sera utilisée. Ni est parfait. (Remarquez, ce dernier problème existe aussi pour les exemples; si chaque exemple utilisé dans le test utilise des listes sérialisables, rien ne s'oppose à ce qu'une version non sérialisable soit utilisée par un autre code en pratique).
J'ai essayé d'écrire un test unitaire (dans Groovy utilisant Spock) qui permet de vérifier qu'une interface donnée à utiliser avec RMI est en fait entièrement sérialisable - tous les paramètres, exceptions et implémentations possibles des types définis dans les méthodes.
Cela semble fonctionner pour moi jusqu'à présent, cependant, c'est un peu délicat à faire et il peut y avoir des cas que cela ne couvre pas, alors utilisez-le à vos risques et périls!
Vous devrez remplacer les exemples d'interface Notification
etc. par les vôtres. L'exemple inclut un champ non sérialisable à titre d'illustration.
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
}
}