问题
I've thousands of classes in our java project. Some of them implements serializable interface. Now here's a problem. It's possible someone can go in a class, add new variable that is neither transient nor serializable. Code compiles fine however process would blow up at runtime.
To illustrate this
class Foo implements Serializable { .... // all good }
class Foo implements Serializable
{
// OOps, executorService is not serializable. It's not declared as transient either
private ExecutorService executorService = ..
}
I'm thinking about writing a unit test that would go thru all classes and ensure "true serializability". I've read some discussions about serialing specific objects. i understand that process but it requires
1) creating an object.
2) serializing and then
3) deserializing.
Is there more efficient and practical approach. perhaps to use reflection. Go thru all classes, if class has serializable then all attributes must be serializable or have transient keyword..
Thoughts ?
回答1:
1) creating an object. 2) serializing and then 3) deserializing.
This list is not complete; you also need initialization. Consider the example:
class CanBeSerialized implements Serializable {
private String a; // serializable
private Thread t; // not serializable
}
class CannotBeSerialized implements Serializable {
private String a; // serializable
private Thread t = new Thread(); // not serializable
}
You can serialize and deserialize the first one, but you'll get NotSerializableException
on the second. To complicate the matter further, if interfaces are used, you can never tell if a class will pass serialization, as it's the concrete object of the class behind this interface that will be streamed:
class PerhapsCanBeSerializedButYouNeverKnow implements Serializable {
private Runnable r; // interface type - who knows?
}
Provided that you could guarantee the following for all your classes and classes used by your classes to be tested:
- default constructor exists,
- no interface types in fields,
then you could automatically create and initialize them by reflection, and then test serialization. But that is a really hard condition, isn't it? Otherwise, proper initialization is down to manual work.
You could use reflection in a different way: iterating through a list of Class
objects you want to check, getting the Field[]
for them, and verifying if they're transient (Field.getModifiers()
) or if they implement Serializable
directly (Field.getType().getInterfaces()
) or indirectly (via a super interface or class). Also, consider how deep you want to check, depending on how deep your serialization mechanism works.
As Ryan pointed out correctly, this static serialization check would fail if the code was evil enough:
class SeeminglySerializable implements Serializable {
// ...
private void writeObject/readObject() {
throw new NotSerializableException();
}
}
or just if readObject()/writeObject()
were badly implemented. To test against this kind of problems, you need to actually test the serialization process, not the code behind it.
回答2:
If serialization is a key part of your app, then include the serialization in your tests. Something like:
@Test
public void aFooSerializesAndDeserializesCorrectly {
Foo fooBeforeSerialization = new Foo();
ReflectionUtils.randomlyPopulateFields(foo);
Foo fooAfterSerialization = Serializer.serializeAndDeserialize(foo);
assertThat(fooAfterSerialization, hasSameFieldValues(fooBeforeSerialization));
}
Edit: A trivial implementation of randomlyPopulateFields
:
public static void randomlyPopulateFields(final Object o) {
ReflectionUtils.doWithFields(o.getClass(), new ReflectionUtils.FieldCallback() {
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, o, randomValueFor(field.getType()));
}
private Random r = new Random();
private Object randomValueFor(Class<?> type) {
if (type == String.class) {
return String.valueOf(r.nextDouble());
} else if (type == Boolean.class || type == Boolean.TYPE) {
return r.nextBoolean();
} else if (type == Byte.class || type == Byte.TYPE) {
return (byte) r.nextInt();
} else if (type == Short.class || type == Short.TYPE) {
return (short) r.nextInt();
} else if (type == Integer.class || type == Integer.TYPE) {
return r.nextInt();
} else if (type == Long.class || type == Long.TYPE) {
return (long) r.nextInt();
} else if (Number.class.isAssignableFrom(type) || type.isPrimitive()) {
return Byte.valueOf("1234");
} else if (Date.class.isAssignableFrom(type)) {
return new Date(r.nextLong());
} else {
System.out.println("Sorry, I don't know how to generate values of type " + type);
return null;
}
}
});
}
来源:https://stackoverflow.com/questions/7732259/unit-testing-serializability-for-all-classes-in-java-project