EJB 和 CDI Bean 序列化的最佳实践EJB吉帕断续器静态代码分析器

2022-09-01 23:05:41

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

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

  • 由反序列化注入的字段和在反序列化时重新注入的字段会被重新注入吗?@Inject@PersistenceContext
  • 它们应该被标记为?transient
  • 或者我应该忽略/关闭代码检查?
  • 我真的应该像PMD建议的那样为所有这些字段提供访问器吗?

答案 1

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

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

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

应该注意的是,注入的资源或Bean不必是可序列化的,因为代理将是可序列化的。唯一的例外是@Dependent范围好的bean,它必须是可序列化的或注入瞬态的。这是因为在这种情况下不使用代理。

它们是否应标记为瞬态?

不,见上文。

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

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

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

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


答案 2

这个答案将详细说明 EJB 3.2 (JSR 345)、JPA 2.1 (JSR 338) 和 CDI 1.2 (JSR 346) 的序列化/钝化语义。值得注意的是,Java EE 7 伞形规范 (JSR 342)、托管 Beans 1.0 规范 (JSR 316) 和 Commons Annotations 规范 1.2 (JSR 250) 在序列化/钝化方面没有任何我们感兴趣的内容。

我还将介绍静态代码分析器的主题。

EJB

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

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

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

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

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

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

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

第4.2.1节说:

Bean 提供程序必须假定瞬态字段的内容可能会在“停用前”和“激活后”通知之间丢失。

[...]

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

老实说,要求容器“实现与Java序列化协议等效的结果”,同时使其完全不清楚瞬态字段会发生什么,这是非常可悲的。带回家的教训是,任何事情都不应该被标记为瞬时。对于容器无法处理的字段,请使用 编写 和 还原。@PrePassivatenull@PostActivate

吉帕

“钝化”一词在JPA规范中没有出现。JPA 也没有为 、 、 和 等类型定义序列化语义。规范中唯一与我们相关的句子是这样的(“6.9 查询执行”一节):EntityManagerFactoryEntityManagerQueryParameter

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

断续器

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

一个含义是 - 这是一个伪范围 - 不是一个具有钝化能力的范围。同样值得注意的是,这不是一个钝化能力的范围,无论出于何种原因,大多数互联网似乎都相信。例如,第“17-2.开发JSF应用程序“一书中的”Java 9配方:问题解决方案方法”。@Dependentjavax.faces.view.ViewScoped

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

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

未被定义为“具有钝化能力的 Bean”并不意味着诸如无状态、单例、EntityManagerFactory、EntityManager、Event 和 BeanManager 之类的内容不能用作您创作的具有钝化能力的实例中的依赖项。相反,这些东西被定义为“支持钝化的依赖关系”(参见“6.6.3.支持钝化的依赖关系“和”3.8.其他内置豆”)。

CDI 通过使用具有钝化功能的代理使这些去干扰钝化成为可能(请参阅“5.4.客户端代理“和”7.3.6.资源的生命周期”)。请注意,要使 Java EE 资源(如 EntityManagerFactory 和 EntityManager)具有钝化功能,必须将它们声明为 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