[英]how to test in Java that a class implements Serializable correctly (not just is an instance of Serializable)
我正在實現一個可序列化的類(所以它是一個使用w / RMI的值對象)。 但我需要測試它。 有沒有辦法輕松做到這一點?
澄清 :我正在實現這個類,所以在類定義中堅持使用Serializable是微不足道的。 我需要手動序列化/反序列化它以查看它是否有效。
我發現了這個C#問題 ,Java有類似的答案嗎?
簡單的方法是檢查對象是java.io.Serializable
或java.io.Externalizable
的實例,但這並不能真正證明該對象確實是可序列化的。
唯一可靠的方法是嘗試真實。 最簡單的測試類似於:
new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(myObject);
並檢查它不會拋出異常。
Apache Commons Lang提供了一個相當簡短的版本:
SerializationUtils.serialize(myObject);
再次,檢查異常。
你可以更嚴格,並檢查它反序列化回原來的東西:
Serializable original = ...
Serializable copy = SerializationUtils.clone(original);
assertEquals(original, copy);
等等。
基於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);
}
簡而言之,您可以提出一些候選對象,並嘗試使用您選擇的機制對它們進行序列化。 這里的測試是在編組/解組期間沒有遇到錯誤,並且得到的“再水化”對象等於原始對象。
或者,如果您沒有任何候選對象,則可以實現基於反射的測試,該測試會對類的(非靜態,非瞬態)字段進行內省,以確保它們也是可序列化的。 從經驗來看,這令人驚訝地復雜得令人驚訝,但它可以在合理的范圍內完成。
后一種方法的缺點是,如果一個字段是例如List<String>
,那么你可以使類沒有嚴格可序列化的字段而失敗,或者只是假設將使用List的可序列化實現。 兩者都不完美。 (請注意,后一個問題也存在於示例中;如果測試中使用的每個示例都使用可序列化的列表,那么在實踐中沒有什么可以防止其他代碼使用非序列化版本)。
這段代碼應該這樣做......
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);
}
}
這僅適用於完全填充的對象,如果您要求頂級對象中包含的任何對象也可序列化,則它們不能為null,以使此測試有效,因為序列化/反序列化會跳過空對象
你可以做以下測試:
以下是將對象序列化和反序列化為文件的示例:
我試圖編寫一個單元測試(使用Spock在Groovy中),它可以檢查用於RMI的給定接口實際上是否完全可序列化 - 所有參數,異常以及方法中定義的類型的可能實現。
到目前為止,它似乎對我有用,但是,這有點繁瑣,可能會出現這種情況,所以請自行承擔風險!
您需要將示例接口Notification
等替換為您自己的接口。 該示例包括一個不可序列化的字段作為說明。
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
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.