You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于XDoclet的无状态会话Bean兼容问题:WebSphere 9.0.5中ClassCastException

问题背景

有一个基于Java 1.6开发、部署在WebSphere 8.5的遗留EJB应用,升级至Java 1.8 + WebSphere 9.0.5后,特定XDoclet驱动的无状态会话Bean出现异常:

  • 该Bean标注@ejb.util generate = "physical",当批处理任务先启动并创建Bean,后续应用调用时抛出ClassCastException:
    Caused by: java.lang.ClassCastException: org.omg.stub.javax.ejb._EJBHome_Stub incompatible with com.xxx.common.reference.interfaces.ReferenceDataBeanHome
    
  • 若应用先创建Bean,批处理再使用则正常。
  • XDoclet生成的窄化代码无法修改,代码如下:
    private static Object lookupHome(java.util.Hashtable environment, String jndiName, Class narrowTo) throws javax.naming.NamingException {
          // Obtain initial context
          javax.naming.InitialContext initialContext = new javax.naming.InitialContext(environment);
          try {
             Object objRef = initialContext.lookup(jndiName);
             // only narrow if necessary
             if (narrowTo.isInstance(java.rmi.Remote.class))
                return javax.rmi.PortableRemoteObject.narrow(objRef, narrowTo);
             else
                return objRef;
          } finally {
             initialContext.close();
          }
       }
    
  • 尝试改为@ejb.util generate = "logical"后,应用先启动时批处理任务会失败。

问题解答

1. 批处理先启动时ClassCastException的原因

核心原因是类加载器隔离导致的类型不兼容:

  • 当@ejb.util generate = "physical"时,WebSphere会将EJBHome的存根(_EJBHome_Stub)直接绑定到物理JNDI节点。
  • 批处理任务和主应用通常运行在不同的类加载器上下文:批处理先启动时,存根类由批处理的类加载器加载;后续主应用lookup时,拿到的是批处理类加载器加载的_EJBHome_Stub实例,而主应用的ReferenceDataBeanHome接口由自身应用类加载器加载。
  • JVM中,同一个类名如果由不同类加载器加载,会被视为完全不同的类型,因此强转时抛出ClassCastException。
  • 反之,应用先启动时,存根由应用类加载器加载,批处理lookup时会复用该类加载器的类,类型匹配,强转成功。

2. 确保两种初始化顺序兼容的方案

方案1:共享EJB接口类到全局类加载器

将com.xxx.common.reference.interfaces包下的所有EJB接口类(包括ReferenceDataBeanHome)部署到WebSphere的**共享库(Shared Library)**中:

  • 在WebSphere管理控制台中创建共享库,将接口类的JAR包添加进去。
  • 配置主应用和批处理任务的类加载器,让它们都引用这个共享库,且设置共享库的类加载器优先级高于应用自身的类加载器。
  • 这样无论谁先启动,接口类和存根类都由同一个全局类加载器加载,从根源避免类加载器隔离导致的类型冲突。

方案2:包装JNDI查找逻辑,强制窄化

由于XDoclet生成的代码无法修改,可以在调用lookupHome的外层添加包装逻辑,捕获ClassCastException后强制执行窄化操作:

public static Object safeLookupHome(java.util.Hashtable environment, String jndiName, Class narrowTo) throws javax.naming.NamingException {
    try {
        return lookupHome(environment, jndiName, narrowTo);
    } catch (ClassCastException e) {
        // 强制使用PortableRemoteObject窄化,绕过原代码的判断逻辑
        javax.naming.InitialContext initialContext = new javax.naming.InitialContext(environment);
        try {
            Object objRef = initialContext.lookup(jndiName);
            return javax.rmi.PortableRemoteObject.narrow(objRef, narrowTo);
        } finally {
            initialContext.close();
        }
    }
}
  • 原代码中narrowTo.isInstance(java.rmi.Remote.class)的判断逻辑存在缺陷:ReferenceDataBeanHome作为EJBHome接口,是Remote的子接口,但isInstance判断的是narrowTo类是否是Remote的实例(而非是否是其子类),导致跳过了窄化步骤,直接返回未窄化的存根,引发类型转换失败。强制窄化可以修复这个逻辑问题。

方案3:同时配置物理和逻辑JNDI绑定

保留@ejb.util generate = "physical",同时通过WebSphere管理控制台手动添加逻辑JNDI绑定:

  1. 进入EJB管理页面:应用程序 -> 企业应用程序 -> 目标应用 -> 管理EJB模块 -> 目标会话Bean -> 绑定
  2. 添加新的逻辑JNDI名称,指向该EJB的Home接口。
  3. 让批处理任务使用物理JNDI名,主应用使用逻辑JNDI名(或反过来),两者通过不同的JNDI节点获取EJB实例,避免类加载器冲突。

方案4:统一类加载器上下文

  • 调整批处理任务的启动时机,配置为在主应用完全启动后再执行,确保存根类由应用类加载器加载。
  • 如果批处理是通过WebSphere的调度器执行,可修改批处理的类加载器配置,让它复用主应用的类加载器。

内容的提问来源于stack exchange,提问作者HariHaravelan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 16:42:30