这个答案将详细说明 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 规范明确指出,对诸如UserTransaction
、EntityManagerFactory
和容器管理之类的事物的引用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
和@PostActivate
restore。
JPA
JPA 规范中没有出现“钝化”一词。EntityManagerFactory
JPA 也没有为、EntityManager
和Query
等类型定义序列化语义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
.