【问题标题】:How to handle a Findbugs "Non-transient non-serializable instance field in serializable class"?如何处理 Findbugs“可序列化类中的非瞬态不可序列化实例字段”?
【发布时间】:2011-06-19 04:11:11
【问题描述】:

考虑下面的课程。如果我对它运行 Findbugs,它会在第 5 行但不在第 7 行给我一个错误(“可序列化类中的非瞬态非可序列化实例字段”)。

1 public class TestClass implements Serializable {
2
3  private static final long serialVersionUID = 1905162041950251407L;
4
5  private Set<Integer> mySet;      // Findbugs error
6
7  private HashSet<Integer> myOtherSet;
8
9 }

这是正确的,因为 java.util.Set 从未在其层次结构中实现 Serializable 而 java.util.HashSet 实现了。 但是,最佳实践是针对接口而不是具体实现进行编码。

我怎样才能最好地处理这个问题?

我可以在第 3 行添加一个 @Suppresswarnings(justification="No bug", values="SE_BAD_FIELD")。我的实际代码中有很多集合和列表,我担心它会乱扔我的代码太多了。

有更好的方法吗?

【问题讨论】:

  • 如果我们在序列化类中使用 byte[] 导致上述问题该怎么办?
  • 我目前无法在此 Java 代码上触发此错误。是否修改了 findbugs 行为?

标签: java serialization findbugs


【解决方案1】:

我对可序列化类中的受保护字段发出高警告。为该字段添加瞬态解决了我的问题:

 protected transient Object objectName;

【讨论】:

    【解决方案2】:

    如果您使用 findbugs-maven-plugin 并且必须持久化一个字段,并且该字段是一个未实现 Serializable 接口的类,例如,一个具有在第 3 方中定义的类的字段。您可以手动为 findbugs 配置排除文件,

    如果这是唯一的情况,请将其添加到排除文件中: pom:

    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>findbugs-maven-plugin</artifactId>
        <version>3.0.3</version>
        <configuration>
              <xmlOutput>true</xmlOutput>
              <xmlOutputDirectory>target/findbugs/</xmlOutputDirectory>
              <excludeFilterFile>findbugs-exclude.xml</excludeFilterFile>
              <includeFilterFile>findbugs-include.xml</includeFilterFile>
              <failOnError>true</failOnError>
        </configuration>
    ...
    

    exclude.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <FindBugsFilter>
        <Match>
            <Class name="com.xxx.Foo" /> 
            <Field type="org.springframework.statemachine.StateMachineContext"/>
        </Match>
    

    实体:

    @Entity
    public class Foo extends Boo {
        StateMachineContext<A, B> stateMachineContext;
    

    虽然我不明白为什么添加&lt;Bug category="SE_BAD_FIELD"/&gt; 不起作用。另外我不赞成@edu.umd.cs.findbugs.annotations.SuppressWarnings(justification="No bug", values="SE_BAD_FIELD")这种在字段上加注解的方案,因为构建工具最好不要穿透业务代码。maven plugin usage&findbugs filters both include and exclude

    关于SE_BAD_FIELD: Non-transient non-serializable instance field in serializable class,我认为它不应该检查实体。因为,javax.persistence.AttributeConverter 提供了外部序列化字段的方法(实现 Serializable 是内部序列化方法)。

    【讨论】:

      【解决方案3】:

      为您的内部表示使用具体的可序列化集,但让任何公共接口都使用 Set 接口。

      public class TestClass implements Serializable {
          private static final long serialVersionUID = 1905162041950251407L;
      
          private HashSet<Integer> mySet;
      
          public TestClass(Set<Integer> s) {
              super();
              setMySet(s);
          }
      
          public void setMySet(Set<Integer> s) {
              mySet = (s == null) ? new HashSet<>() : new HashSet<>(s);
          }
      }
      

      【讨论】:

        【解决方案4】:

        不过,最好的做法是编写代码 反对接口而不是具体 实现。

        我认为不,在这种情况下不是。 Findbugs 非常正确地告诉您,一旦您在该字段中具有不可序列化的Set 实现,您就有可能遇到NotSerializableException。这是你应该处理的事情。如何,这取决于你的类的设计。

        • 如果这些集合在类中初始化并且从未从外部设置,那么我认为声明该字段的具体类型绝对没有问题,因为无论如何字段都是实现细节。请务必在公共接口中使用接口类型。
        • 如果集合通过公共接口传递到类中,您必须确保它们实际上是Serializable。为此,请创建一个接口 SerializableSet extends Set, Serializable 并将其用于您的领域。然后,要么:
          • 在公共接口中使用SerializableSet,并提供实现它的实现类。
          • 检查通过instanceof Serializable 传递给类的集合,如果不是,则将它们复制到存在的东西中。

        【讨论】:

        • 新。即使在这种情况下,我也不喜欢使用具体类型。我认为这是一个可以安全忽略的警告。您可能需要担心的唯一部分是您是否真的有任意代码设置此集合,这可能会将其设置为不可序列化的集合实例。
        • @Michael 在内部使用具体类型可能不是“问题”,但我认为这是一种不好的做法。即使它是在外部设置的,您也只需要担心您可能正在处理您无法控制的代码。我觉得在这种情况下,设计的简洁性(使用界面)超过了这个警告的(理论上)有用性。
        • 我同意@jtahlborn。当你真的需要一个集合时,你不能让每个方法都只接受 HashSet。调用者不应该被要求传递一个 HashSet,任何 Serializable Set 实现都可以。这是你现在无法用 Java 表达的东西,你必须处理的语言设计限制。我认为使用界面并忽略此警告会更安全(或者只是检查您是否还好,而不是 100% 确定)。
        • @ymajoros:对于您的陈述而言,“安全”一词没有合理的定义。实际上,安全性正是使用具体类型比忽略警告更好的解决方案的原因。
        • @Michael - 具体类型并不比接口更安全,除非您正在处理“最终”类型。我可以轻松地创建一个自定义的 HashSet 子类,它是 not 可序列化的,因此编译检查不会为您带来任何好处。如果您真的很偏执,那么您永远不应该直接从外部实例设置集合,而始终制作防御性副本。无论哪种方式,我关于使用界面的 cmets 仍然有效:因此警告没有帮助。
        【解决方案5】:

        您可以通过将以下方法添加到您的类中来摆脱那些Critical 警告消息:

        private void writeObject(ObjectOutputStream stream)
                throws IOException {
            stream.defaultWriteObject();
        }
        
        private void readObject(ObjectInputStream stream)
                throws IOException, ClassNotFoundException {
            stream.defaultReadObject();
        }
        

        【讨论】:

        • 很好的发现!这解决了一个非常模糊的 findbugs 错误。正如罗德里戈指出的那样,还避免了标记瞬态的需要,这甚至没有帮助。
        • 工作清单魅力!谢谢!
        • 添加过时的代码以使审计工具满意(显然利用了该工具中的错误),而程序行为保持不变,只是效率稍低。好东西……
        • 如果您测量它,您可能会看到 10 或 100 纳秒的开销。你测量的时候有没有得到不同的结果?
        • 引入的新错误(例如不必要地实现了默认流式处理行为)怎么样?
        【解决方案6】:

        我对集合字段使用 findbugs-exclude 过滤器:

        <Match>
            <Field type="java.util.Map" />
            <Bug pattern="SE_BAD_FIELD" />
        </Match>
        <Match>
            <Field type="java.util.Set" />
            <Bug pattern="SE_BAD_FIELD" />
        </Match>
        <Match>
            <Field type="java.util.List" />
            <Bug pattern="SE_BAD_FIELD" />
        </Match>
        

        http://findbugs.sourceforge.net/manual/filter.html

        【讨论】:

        • 这不是错误!所有已知的实现都是可序列化的。 Java 不支持定义像“type SerializableList = java.util.List & Serializable”这样的新类型。并且创建一个接口并不能解决问题,因为例如:ArrayList 是可序列化的和一个列表,但与您的接口不匹配。
        • @brabenetz 什么?那是认真的回应吗?你真的是想告诉我你认为你做不到interface Foo extends Serializable, List吗? Java 确实支持定义新类型……这就是 Java 的真正用途……这是任何 OO 语言的商标……
        • @searchengine27 确定您可以定义自己的接口“Foo”,但 JDK 中的任何 List 实现都不会实现您的接口“Foo”。您可以 NOT 在 java (AFAIK) 中定义匿名类型,例如“private Set & Serializable mySet;”您可以使用泛型(正如 jontejj 在 stackoverflow.com/a/10473306/702345 中描述的那样),但泛型有其自身的问题,在这种情况下会使您的代码难以阅读、编写和使用。
        • 你不懂。仅仅因为您在 JavaSE API 中找不到这样的类,并不意味着:1)它永远不会存在于 JavaSE API 中,2)JavaEE、JavaFX、JavaME 等等等等等等等等等等等等等,都没有这样的一个集合 3)您的代码没有使用具有此类集合的第三方库,4)您自己的代码没有定义这样的类。如果你开始闭上眼睛,就像 dounyy 所说的那样,那么你将不可避免地开始错过 Findbugs 告诉你的一些重要事情。
        • 当然我明白,但我优先考虑“保持简单”的规则高于此规则。最后,它取决于您的项目。在开源项目中,您可能会受益于额外的复杂性。但是在封闭源项目中,您通常知道谁访问了您的 API(如果它甚至是 API)。
        【解决方案7】:

        您可以使用捕获助手来确保传入的 Set 支持两个接口:

        private static class SerializableTestClass<T extends Set<?> & Serializable> implements Serializable
        {
            private static final long serialVersionUID = 1L;
            private final T serializableSet;
        
            private SerializableTestClass(T serializableSet)
            {
                this.serializableSet = serializableSet;
            }
        }
        
        public static class PublicApiTestClass
        {
            public static <T extends Set<?> & Serializable> Serializable forSerializableSet(T set)
            {
                return new SerializableTestClass<T>(set);
            }
        }
        

        通过这种方式,您可以拥有一个强制执行 Serializable 的公共 API,而无需检查/要求特定的实现细节。

        【讨论】:

        • 如果我们在序列化类中使用 byte[] 导致上述问题该怎么办?
        【解决方案8】:

        我知道这是一个已经回答的老问题,但其他人知道,如果您对序列化将修复您的 FindBugs 错误的特定字段不感兴趣,您可以将 Set&lt;Integer&gt; 字段设置为瞬态。

        public class TestClass implements Serializable {
        
            private static final long serialVersionUID = 1905162041950251407L;
            private transient Set<Integer> mySet;
        
        }
        

        我更喜欢这种方法,而不是强制你的 API 的用户强制转换为你的具体类型,除非它只是内部的,那么 Michael Borgwardt 的回答更有意义。

        【讨论】:

        • 这只是将错误更改为“字段 mySet 是暂时的,但不是通过反序列化设置”
        • 是的,使用瞬态只会产生其他 findbugs 错误:SE_TRANSIENT_FIELD_NOT_RESTORED。弗拉德在下面的回答为我解决了这个问题。
        • 他正在标记 TestClass Serializable 。他想将它与字段一起序列化。
        • FWIW,添加 transient 在我的情况下有效。也许自 2015 年以来,这些东西发生了一些变化。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-08-11
        • 1970-01-01
        • 2011-09-26
        • 2012-07-05
        • 2020-03-15
        • 2018-09-12
        • 2016-12-12
        相关资源
        最近更新 更多