EJB和CDI bean序列化的最佳实践

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

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

  • @Inject@PersistenceContext注入的字段重新注入反序列化?
  • 它们应该标记为transient吗?
  • 或者我应该忽略/关闭代码检查?
  • PMD建议我应该真正提供所有这些领域的访问者吗?

这个答案将详细介绍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的字段声明为瞬态。

要求容器“实现与Javas序列化协议相同的结果”,同时让它完全没有说明瞬态字段发生了什么,这是非常可悲的,说实话。 带回家的教训是,任何东西都不应该被标记为短暂的。 对于容器无法处理的字段,请使用@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。开发JSF应用程序”一节。

具有钝化能力的范围要求声明“具有范围的类的实例具有钝化能力”(“6.6.4。钝化范围”部分)。 “6.6.1。具有钝化能力的bean”一节将这样的对象实例定义为可转移到二级存储的对象实例。 特殊的类注释或接口不是明确的要求。

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

未被定义为“具有钝化function的bean”并不意味着诸如无状态,单例,EntityManagerFactory,EntityManager,Event和BeanManager之类的东西不能用作您创作的具有钝化function的实例中的依赖项。 相反,这些东西被定义为“具有钝化能力的依赖关系”(参见“6.6.3。能够依赖钝化的依赖关系”和“3.8。附加内置bean”一节)。

CDI通过使用具有钝化function的代理使这些依赖性能够被钝化(参见“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实例都可以用作类中的实例字段,并使用具有钝化function的范围进行注释。

静态代码分析器

静态代码分析器是愚蠢的。 我认为对于高级开发人员来说,他们更多的是引起关注而不是助手。 这些分析器针对可疑的序列化/钝化问题提出的虚假标志肯定是非常有限的价值,因为CDI要求容器validation实例“真正具有钝化能力,此外,它的依赖性具有钝化能力”或者“抛出” javax.enterprise.inject.spi.DeploymentException的子类“(”6.6.5。对具有钝化function的bean和依赖项的validation“和”2.9。容器自动检测到的问题“)。

最后,正如其他人所指出的那样,值得重复的是:我们应该永远不要将一个字段标记为transient

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

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

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

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

它们应该标记为瞬态吗?

不,见上文。

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

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

PMD建议我应该真正提供所有这些领域的访问者吗?

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

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