如何处理 Findbugs“可序列化类中的非瞬态不可序列化实例字段”?

Posted

技术标签:

【中文标题】如何处理 Findbugs“可序列化类中的非瞬态不可序列化实例字段”?【英文标题】:How to handle a Findbugs "Non-transient non-serializable instance field in serializable class"? 【发布时间】: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 行为? 【参考方案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 在 ***.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 年以来,这些东西发生了一些变化。

以上是关于如何处理 Findbugs“可序列化类中的非瞬态不可序列化实例字段”?的主要内容,如果未能解决你的问题,请参考以下文章

如何处理 MaxUploadSizeExceededException

如何处理 UsernameNotFoundException 春季安全

如何处理c#中的错误代码

Akka 如何处理消息版本?

开玩笑测试 - 如何处理 JsonWebToken 响应

如何处理 JSON 响应