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

时间:2012-12-04 10:02:31

标签: java serialization ejb deserialization cdi

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

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

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

3 个答案:

答案 0 :(得分:25)

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

  

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

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

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

  

它们应该标记为瞬态吗?

不,见上文。

  

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

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

  

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

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

答案 1 :(得分:7)

这个答案将详细介绍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实例是EJB会话实例的唯一类型,它可以使用容器管理的EntityManager和扩展的持久化上下文。此持久性上下文将绑定到@Stateful实例的生命周期,而不是单个JTA事务。

EJB规范没有明确解决带有扩展持久化上下文的容器管理的EntityManager会发生什么。我的理解是这样的:如果有一个扩展的持久化上下文,那么根据之前定义的规则,这个人必须被认为是可序列化的,如果是,则进行钝化。如果钝化继续进行,那么@Stateful类开发人员只需关注自己对应用程序管理的实体管理器的引用。

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

第4.2.1节说:

  

Bean Provider必须假设在PrePassivate和PostActivate通知之间可能会丢失瞬态字段的内容。

     

[...]

     

虽然容器不需要使用Java编程语言的Serialization协议来存储钝化会话实例的状态,但它必须达到相同的结果。一个例外是容器在激活期间不需要重置瞬态字段的值。一般来说,不鼓励将会话bean的字段声明为瞬态。

要求容器达到相同的效果"正如Javas序列化协议同时完全没有说明瞬态字段发生的事情是非常可悲的。带回家的教训是,任何东西都不应该被标记为短暂的。对于容器无法处理的字段,请使用@PrePassivate编写null@PostActivate进行还原。

JPA

"钝化"在JPA规范中没有出现。 JPA也没有为EntityManagerFactoryEntityManagerQueryParameter等类型定义序列化语义。规范中与​​我们相关的唯一一句话就是这一点(#34; 6.9查询执行"):

  

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

CDI

第" 6.6.4节。钝化范围"将钝化范围定义为显式注释@NormalScope(passivating=true)的范围。此属性默认为false。

一个含义是@Dependent - 这是一个伪范围 - 不是一个具有钝化能力的范围。同样值得注意的是,javax.faces.view.ViewScoped不是一个具有钝化能力的范围,无论出于什么原因,大多数互联网似乎都相信这一范围。例如,部分" 17-2。开发JSF应用程序"在书中" Java 9食谱:问题解决方法"。

具有钝化功能的范围要求声明"类的实例具有钝化能力" (第" 6.6.4。钝化范围")。第" 6.6.1节。具有钝化功能的豆类#34;将这样的对象实例简单地定义为可转移到辅助存储的对象实例。特殊的类 - 注释或接口不是明确的要求。

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

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

CDI通过使用具有钝化功能的代理来使这些依赖性能够被钝化(参见第34节中的最后一个项目符号项目; 5.4。客户端代理"以及#34; 7.3.6。资源的生命周期" )。请注意,对于要使其具有钝化功能的Java EE资源(例如EntityManagerFactory和EntityManager),必须将它们声明为CDI生产者字段(第34节“3.7.1。声明资源"”),它们不支持任何除了@Dependent之外的其他范围(参见" 3.7。资源"部分),必须使用@Inject在客户端查找它们。

其他@Dependent实例 - 虽然没有声明具有正常范围,并且不需要由CDI"客户端代理" - 如果实例可以转移到二级存储,即可序列化,则也可以用作具有钝化能力的依赖性。这个人将与客户一起序列化(参见第34节中的最后一个项目符号项; 5.4。客户代理和#34;)。

非常清楚并提供一些例子; @Stateless实例,对CDI生成的EntityManager的引用和可序列化的@Dependent实例都可以用作类中的实例字段,并使用具有钝化功能的范围进行注释。

静态代码分析器

静态代码分析器很愚蠢。我认为对于高级开发人员来说,他们更多的是引起关注而不是助手。这些分析器针对可疑的序列化/钝化问题引发的错误标志当然具有非常有限的价值,因为CDI要求容器验证实例是否真正具有钝化能力,并且此外,其依赖性具有钝化能力"或者以其他方式"抛出javax.enterprise.inject.spi.DeploymentException"的子类。 (第" 6.6.5。验证具有钝化功能的bean和依赖关系"和" 2.9。容器自动检测到的问题")。

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

答案 2 :(得分:1)

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