如何处理 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;
虽然我不明白为什么添加<Bug category="SE_BAD_FIELD"/>
不起作用。另外我不赞成@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您可以使用捕获助手来确保传入的 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<Integer>
字段设置为瞬态。
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