23

我还没有遇到任何与序列化相关的问题。但是 PMD 和 Findbugs 检测到一系列关于序列化的潜在问题。一个典型的案例是被检测为不可序列化的注入记录器。但还有更多 -EntityManager和几个 CDI bean。

我还没有找到任何关于如何正确处理序列化的最佳实践。

  • 将通过反序列化注入@Inject@PersistenceContext重新注入的字段吗?
  • 他们应该被标记为transient
  • 还是我应该忽略/关闭代码检查?
  • 我真的应该按照 PMD 的建议为所有这些领域提供访问器吗?
4

3 回答 3

27

我意识到这是一个老问题,但我相信提供的唯一答案是不正确的。

由@Inject 和@PersistenceContext 注入的字段会在反序列化时重新注入吗?

不,他们不会。我个人在集群环境中使用 JBoss 体验过这一点。如果 bean 具有钝化能力,那么容器必须注入一个可序列化的代理。该代理被序列化和反序列化。一旦反序列化,它将找到正确的注入并重新连接它。但是,如果您将字段标记为瞬态,则代理不会序列化,并且您将在访问注入的资源时看到 NPE。

需要注意的是,注入的资源或 bean 不一定是 Serializable,因为代理会。唯一的例外是必须可序列化或注入瞬态的 @Dependent 范围 bean。这是因为在这种情况下不使用代理。

它们应该被标记为瞬态吗?

不,见上文。

还是我应该忽略/关闭代码检查?

这取决于你,但这是我会做的。

我真的应该按照 PMD 的建议为所有这些领域提供访问器吗?

我不会。在我们的项目中,当我们知道我们正在使用 CDI 时,我们会禁用此检查。

于 2013-11-19T17:57:09.270 回答
8

这个答案将详细说明 EJB 3.2 ( JSR 345 )、JPA 2.1 ( JSR 338 ) 和 CDI 1.2 ( JSR 346 ) 的序列化/钝化语义。值得注意的是,Java EE 7 总体规范 ( JSR 342 )、Managed Beans 1.0 规范 ( JSR 316 ) 和 Commons Annotations 规范 1.2 ( JSR 250 ) 在序列化/钝化。

我还将涉及静态代码分析器的主题。

EJB

相关章节是“4.2 有状态会话 Bean 的会话状态”和“4.2.1 实例钝化和会话状态”。

@Stateless并且@Singleton实例永远不会被钝化。

@Stateful实例可能被钝化。从 EJB 3.2 开始,类开发人员可以使用@Stateful(passivationCapable=false).

EJB 规范明确指出,对诸如UserTransactionEntityManagerFactory和容器管理之类的事物的引用EntityManager由容器负责。除非持久性上下文中的所有实体和 EntityManager 实现都是可序列化的,否则不会钝化使用扩展持久性上下文的 @Stateful 实例。

请注意,应用程序管理的 EntityManager 始终使用扩展的持久性上下文。此外,@Stateful 实例是唯一可以使用具有扩展持久性上下文的容器管理的 EntityManager 的 EJB 会话实例类型。此持久性上下文将绑定到 @Stateful 实例的生命周期,而不是单个 JTA 事务。

EJB 规范没有明确说明容器管理的具有扩展持久性上下文的 EntityManager 会发生什么。我的理解是:如果有一个扩展的持久性上下文,那么这个家伙必须根据之前定义的规则被视为可序列化或不可序列化,如果是,则进行钝化。如果钝化继续进行,那么@Stateful 类开发人员只需关心对应用程序管理的实体管理器的引用。

除了描述我们作为开发人员应该做出的假设之外,EJB 规范没有指定瞬态字段会发生什么。

第 4.2.1 节说:

Bean Provider 必须假定瞬态字段的内容可能会在 PrePassivate 和 PostActivate 通知之间丢失。

[...]

虽然容器不需要使用 Java 编程语言的序列化协议来存储钝化会话实例的状态,但它必须达到等效的结果。一个例外是容器不需要在激活期间重置瞬态字段的值。通常,不鼓励将会话 bean 的字段声明为瞬态。

老实说,要求容器“达到与 Java 序列化协议相同的结果”,同时完全没有说明瞬态字段会发生什么,这是非常可悲的。带回家的教训是,不应将任何东西标记为瞬态。对于容器不能处理的字段,使用@PrePassivate写anull@PostActivaterestore。

JPA

JPA 规范中没有出现“钝化”一词。EntityManagerFactoryJPA 也没有为、EntityManagerQuery等类型定义序列化语义Parameter。规范中与​​我们相关的唯一一句话是(“6.9 查询执行”部分):

CriteriaQuery、CriteriaUpdate 和 CriteriaDelete 对象必须是可序列化的。

CDI

“6.6.4.钝化作用域”节将钝化作用域定义为显式注释的作用域@NormalScope(passivating=true)。此属性默认为 false。

一个暗示是@Dependent——这是一个伪作用域——不是一个能够钝化的作用域。同样值得注意的是,javax.faces.view.ViewScoped无论出于何种原因,大多数互联网似乎都相信这不是一个能够钝化的范围。例如,“Java 9 Recipes: A Problem-Solution Approach”一书中的“17-2. Developing a JSF Application”一节。

具有钝化能力的作用域要求声明“具有该作用域的类的实例具有钝化能力”(“6.6.4. 钝化作用域”一节)。“6.6.1. 具有钝化能力的 bean” 节将这样的对象实例简单地定义为可转移到辅助存储的对象实例。特殊的类注释或接口不是明确的要求。

EJB 的实例:s @Stateless 和 @Singleton 不是“具有钝化能力的 bean”。@Stateful 可能是(有状态是唯一让 CDI 管理生命周期的 EJB 会话类型 - 即,永远不要将 CDI 范围放在 @Stateless 或 @Singleton 上)。如果其他“托管 bean”及其拦截器和装饰器都是可序列化的,则它们只是“具有钝化能力的 bean”。

不被定义为“支持钝化的 bean”并不意味着无状态、单例、EntityManagerFactory、EntityManager、Event 和 BeanManager 等内容不能用作您编写的支持钝化的实例中的依赖项。这些东西被定义为“支持钝化的依赖项”(参见“6.6.3. 支持钝化的依赖项”和“3.8. 附加的内置 bean”部分)。

CDI 通过使用具有钝化功能的代理使这些依赖项具有钝化能力(请参阅“5.4. 客户端代理”和“7.3.6. 资源生命周期”一节中的最后一个项目符号项)。请注意,要使 EntityManagerFactory 和 EntityManager 等 Java EE 资源具有钝化能力,它们必须声明为 CDI 生产者字段(“3.7.1. 声明资源”部分),它们不支持除 @Dependent 之外的任何其他范围(请参阅“3.7. 资源”部分)并且必须在客户端使用 @Inject 查找它们。

其他@Dependent 实例——尽管没有以正常范围声明并且不需要以CDI“客户端代理”为前端——如果该实例可转移到辅助存储(即可序列化),也可以用作具有钝化能力的依赖项。这个家伙将与客户端一起序列化(请参阅“5.4.客户端代理”部分中的最后一个项目符号项)。

为了完全清楚并提供一些例子;@Stateless 实例、对 CDI 生成的 EntityManager 的引用和可序列化的 @Dependent 实例都可以用作类中的实例字段,并带有钝化能力范围的注释。

静态代码分析器

静态代码分析器是愚蠢的。我认为对于高级开发人员来说,他们与其说是助手,不如说是令人担忧的原因。这些分析器针对可疑的序列化/钝化问题引发的错误标志的价值肯定非常有限,因为 CDI 要求容器验证实例“确实具有钝化能力,此外,它的依赖项具有钝化能力”或“抛出javax.enterprise.inject.spi.DeploymentException 的子类”(“6.6.5. 钝化能力的 bean 和依赖项的验证”和“2.9. 容器自动检测到的问题”一节)。

最后,正如其他人所指出的,值得重复一遍:我们可能永远不应该将字段标记为transient.

于 2017-08-14T19:30:47.320 回答
1

PMD 和 FindBugs 只检查接口,也没有关于代码运行环境的信息。要使工具安静下来,您可以将它们标记为瞬态,但它们都将在反序列化时正确地重新注入,并且无论瞬态关键字如何都将首次使用。

于 2012-12-05T06:50:21.797 回答