使用会话访问类型的 Spring 代理 bean 时出现随机 ClassCastException

问题描述

我有一个用 Java8 编写的 JEE 应用程序,它使用 Spring 4.3.24。由于我的应用程序的前端使用的是 JSF 2.x,我还使用了自定义 spring 范围 - 第三方库提供的对话访问,即 1.4 版中的 myfaces-orchestra

由于应用程序被 Selenium 测试广泛覆盖,我目前正在分析在多线程中运行测试的主题。详细说明:一个托管应用程序服务器的 JVM - 在我的例子中是 WebSphere 8.5.5,一个使用 JUnit 4.10 的 JVM 在多个线程中运行 selenium 测试。

我面临的问题,但仅在并行运行测试时,偶尔 ClassCastException 在尝试与对话访问 bean 交互时被抛出 cglib 类。

异常如下所示:

Exception: java.lang.classCastException: com.sun.proxy.$Proxy499 incompatible with some.package.PagebackingBean
    at some.package.PagebackingBean$$FastClassBySpringcglib$$ecd1ff4d.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.cglibAopProxy$cglibMethodInvocation.invokeJoinpoint(cglibAopProxy.java:736)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:133)
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:121)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.cglibAopProxy$DynamicAdvisedInterceptor.intercept(cglibAopProxy.java:671)

如前所述,异常只会不时发生。另外,可以注意到,这通常发生在两个或多个线程在同一毫秒内引用相同类型的会话 bean 的情况下(当然,由于这两个线程使用不同的会话,底层 bean 是不同的)

我已经排除的是:

  1. 这不是与底层 bean 初始化相关的问题。尽管 bean 具有 postconstruct,但它已成功初始化
  2. 我认为这个问题是在升级到 spring 4.x(从 3.x 开始)时提出的,所以我尝试禁用 Objenesis(通过将 spring.objenesis.ignore 设置为“true”),但这也没有帮助

解决方法

经过进一步调查,我想我找到了问题的根本原因 - 它位于 Orchestra 实现中

基本上, Orchestra 每次实例化会话访问 bean 时,都会操纵 bean 定义属性 - 它试图设置

AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE=Boolean.TRUE

据我所知,这个设置告诉 AOP 创建 bean 的 CGLIB 代理。由于 bean 定义中的 bean 属性保存在标准的 HashMap 中,因此这种方法会导致竞争条件,最终导致对话访问 bean 的双重代理