【问题标题】:Best practice for serialization for EJB and CDI beansEJB 和 CDI bean 序列化的最佳实践
【发布时间】:2012-11-21 22:12:48
【问题描述】:

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

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

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

【问题讨论】:

    标签: java serialization ejb deserialization cdi


    【解决方案1】:

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

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

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

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

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

    不,见上文。

    或者我应该忽略/关闭代码检查?

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

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

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

    【讨论】:

    • Gavin King wrote 注入的 EJB 或资源应该/不能像您所说的那样标记为瞬态。但是像记录器或 ResourceBundle 这样的对象呢?焊接forces me to mark them transient 否则我会收到 IllegalProductException。如果我说得对,如果标记为瞬态,这将导致 NPE。那么正确的方法是什么?
    • 您是否尝试过请求确定您的 ResourceBundle 生产者的范围?它依赖于 FacesContext,所以无论如何它可能应该是请求范围的。您可能会收到有关序列化的警告的原因是它看起来是 @Dependent 范围(默认),因此必须满足所有序列化要求。
    • 哦,在记录器上,我个人摆脱了注入记录器。如果它们不是可序列化的但又希望它们是 Dependent 作用域,那么您会遇到问题。
    • 您的建议可能适用于上述示例。但这些只是实际问题的解决方法。我仍然想注入一个第 3 方不可序列化的对象。有谁知道处理这种情况的假定方法是什么?
    • 你为什么不能尝试 RequestScope 为你的库的生产者方法?这应该会产生一个可序列化的代理。即创建一个具有对不可序列化实例的引用的生产者类,并将 @RequestScope 放在返回值的生产者方法上。这应该会导致注入一个可序列化的代理。
    【解决方案2】:

    此答案将详细说明 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写一个null@PostActivate来恢复。

    JPA

    JPA 规范中没有出现“钝化”一词。 JPA 也没有为EntityManagerFactoryEntityManagerQueryParameter 等类型定义序列化语义。规范中与​​我们相关的唯一一句是(“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

    【讨论】:

    • Static code analyzers are stupid. 绝对不是。任何认为这意味着他们不知道如何阅读它们的人。 Java 静态分析过于简单,因为 Java 是一种安全的开发语言,这最终导致任何人都缺乏开发像样的静态分析工具的市场。静态分析远非“愚蠢”,仅仅因为您不了解它告诉您的内容并不能强制执行该意见。 Findbugs 是一个免费工具——你为你得到的东西付费。它不知道 EJB 规范,我也不希望它知道。这并不会使所有静态分析变得愚蠢。
    【解决方案3】:

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

    【讨论】:

    • 谢谢您,LightGuard。听起来很轻松 :) 你能不能这么好心地添加一个引用来支持这个 - 我已经搜索了很多,但找不到任何明确的东西。
    • 如果我有的话。我的想法是规范本身(尤其是现在公开草案中的 1.1 版)或 Weld 文档。
    猜你喜欢
    • 2011-05-13
    • 1970-01-01
    • 2013-07-27
    • 2022-01-03
    • 2012-08-03
    • 2015-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多