【问题标题】:how to test in Java that a class implements Serializable correctly (not just is an instance of Serializable)如何在 Java 中测试一个类是否正确实现了 Serializable(不仅仅是 Serializable 的实例)
【发布时间】:2010-10-01 14:58:35
【问题描述】:

我正在实现一个可序列化的类(因此它是一个使用 RMI 的值对象)。但我需要测试它。有没有办法轻松做到这一点?

澄清:我正在实现该类,因此将 Serializable 粘贴在类定义中是微不足道的。我需要手动对其进行序列化/反序列化以查看它是否有效。

我找到了这个C# question,Java 有类似的答案吗?

【问题讨论】:

  • 您是否正在尝试测试对象是否正确序列化和反序列化?

标签: java serialization


【解决方案1】:

简单的方法是检查对象是java.io.Serializablejava.io.Externalizable 的实例,但这并不能真正证明该对象确实是可序列化的。

唯一可以确定的方法是真正尝试一下。最简单的测试是这样的:

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

并检查它不会引发异常。

Apache Commons Lang 提供了一个更简短的版本:

SerializationUtils.serialize(myObject);

再次检查异常。

你还可以更加严格,并检查它是否反序列化回与原始相同的东西:

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

等等。

【讨论】:

  • 以防万一有人好奇或不想包含 Apache Commons 库,Spring 还提供带有方法 serializedeserialize 的 SerializationUtils。见SerializationUtils
  • 您的回答对我有帮助,但 deserialize()clone() 返回 Object,而不是 Serializable
  • 在旧的 commons-lang v2 中是这样,但从 v3 开始就没有了 - commons.apache.org/proper/commons-lang/javadocs/api-3.4/org/…
  • 即使这也不是万无一失的。如果一个对象有不可序列化的字段,但它们是null,它将是可序列化的;初始化它们,它不会。
【解决方案2】:

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

【讨论】:

  • ++感谢您和 skaffman!我一直在寻找这样的东西来添加到测试工具中,以阻止开发人员编写不可序列化的代码,并在他们到达我们的集群服务器后终止我们的应用程序。一个救生员!谢谢大家!
  • 更多关于“酸洗”术语的信息:docs.python.org/2/library/pickle.html
  • 简单。优雅的。谢谢你……我假设复制和粘贴没问题。
【解决方案3】:

简短的回答是,您可以提出一些候选对象,并实际尝试使用您选择的机制对它们进行序列化。这里的测试是在编组/解组期间没有遇到错误,并且生成的“重新水化”对象与原始对象相同。

或者,如果您没有任何候选对象,您可以实现基于反射的测试,该测试会内省您的类的(非静态、非瞬态)字段,以确保它们也是可序列化的。从经验来看,这会出奇地复杂地快速完成,但可以在合理的范围内完成。

后一种方法的缺点是,如果一个字段是例如List&lt;String&gt;,那么您可以因为没有严格可序列化的字段而使该类失败,或者只是假设将使用 List 的可序列化实现。两者都不完美。 (请注意,后一个问题也存在于示例中;如果测试中使用的每个示例都使用可序列化列表,则没有什么可以阻止其他代码在实践中使用不可序列化版本。

【讨论】:

    【解决方案4】:

    这仅适用于完全填充的对象,如果您要求顶级对象中组成的任何对象也是可序列化的,那么它们不能为空,因为序列化/反序列化会跳过空对象,因此此测试有效

    【讨论】:

      【解决方案5】:

      这段代码应该可以做到...

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

      【讨论】:

      • instanceof Externalizable 暗示 instanceof Serializable。您无需同时测试两者。
      【解决方案6】:

      你可以做以下测试:

      • 将对象序列化为文件并生成 确保没有抛出异常。
      • 另外,反序列化对象并比较 与原始对象。

      这是将对象序列化和反序列化到文件的示例:

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

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

      【讨论】:

        【解决方案7】:

        我试图编写一个单元测试(在 Groovy 中使用 Spock),它可以检查用于 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
            }
        
        }
        

        【讨论】:

          猜你喜欢
          • 2019-09-23
          • 2013-10-13
          • 1970-01-01
          • 1970-01-01
          • 2012-02-26
          • 2011-05-28
          • 1970-01-01
          • 2023-03-07
          • 1970-01-01
          相关资源
          最近更新 更多