一尘不染

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

java

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

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

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

阅读 226

收藏
2020-12-03

共1个答案

一尘不染

该答案将详细描述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

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

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

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

EJB规范明确指出来的东西,如引用UserTransactionEntityManagerFactory和容器管理EntityManager由容器的照顾。除非持久化上下文中的所有实体以及EntityManager实现可序列化,否则使用扩展的持久化上下文的@Stateful实例将不会被钝化。

请注意,由应用程序管理的EntityManager始终使用扩展的持久性上下文。而且,@
Stateful实例是唯一的EJB会话实例类型,可以使用具有扩展持久性上下文的容器管理的EntityManager。此持久性上下文将绑定到@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无论出于何种原因,大多数Internet似乎都认为这不是具有钝化能力的范围。例如,“
Java 9食谱:一种问题解决方法”一书中的“ 17-2。开发JSF应用程序”一节。

具有钝化能力的作用域要求声明为“具有该作用域的类具有钝化能力”的类的实例(第6.6.4节“钝化作用域”)。“
6.6.1。具有钝化能力的Bean”部分将此类对象实例定义为一个可转移到辅助存储的对象实例。特殊的类注释或接口不是明确的要求。

EJB:@Stateless和@Singleton的实例不是“具有钝化能力的bean”。@Stateful可能是(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。资源生命周期”中的最后一个项目符号)。请注意,要使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

2020-12-03