ClassLoader被绑架,移花接木轻松拿下

问题现象
在 Geronimo 中,部署了一个 HelloWorldGBean,在 HelloWorldGBean 的 hello() 方法中,要调用另外一个 EJB。


当在 Geronimo 中部署一个 GBean 时,Geronimo 会自动生成一个相应的 MBean。MBean 的名字可以通过部署计划中的 gbeanName 来指定:
      <?xml version="1.0" encoding="UTF-8"?>
      <configuration
          xmlns="http://geronimo.apache.org/xml/ns/deployment-1.0"
          configId="wakan/helloworld">
       <dependency>
           <uri>hello/helloworld/1.0/jar</uri>
       </dependency>
       <dependency>
           <uri>openejb/openejb-core/2.0/jar</uri>
       </dependency>
        <gbean gbeanName="wakan:type=HelloWorld" name="helloworld" class="HelloWorld"></gbean>
      </configuration>
在 MC4J 中,找到 HelloWorldGBean 自动转变过来的 MBean。然后调用它的 hello() 方法,Geronimo 的系统控制台上却报错,错误发生在 InitialContext() 时:
java.lang.ClassNotFoundException: org.openejb.client.RemoteInitialContextFactory
奇怪!JNDI 怎么不能初始化了?试着把相同的代码放到 doStart() 方法中,却发现一切正常,能连接上 EJB 并完成调用。
为什么?为什么同样的代码,在 doStart() 方法中和在 hello() 方法中,结果会不同呢?
分析步骤
带着疑惑,追踪分析 JDK 的源码中,关于 IntailContext 的初始化过程的代码。最终在 com.sun.naming.internal.VersionHelper12 中,发现了如下代码:
    ClassLoader getContextClassLoader() {
       return (ClassLoader) AccessController.doPrivileged(
             new PrivilegedAction() {
                public Object run() {
                    return Thread.currentThread().getContextClassLoader();
                }
             }
       );
    }
原来,访问 EJB 时需要的 InitialContextFactory 的具体实现类,是由 Thread.currentThread().getContextClassLoader() 获得的 ClassLoader 来读取的。那么,在 doStart() 方法中和在 hello() 方法中,这个 ClassLoader 分别是什么呢?
在 doStart() 和 hello() 中都加上如下语句:
      ClassLoader cl = Thread.currentThread().getContextClassLoader();
      System.out.println("ClassLoader=" + cl);
重新部署 HelloWorldGBean,可以看到 doStart() 方法被执行,控制台上打印出来的信息是:
      ClassLoader=[org.apache.geronimo.kernel.config.MultiParentClassLoader id=wakan/helloworld]
      Class = class org.openejb.client.RemoteInitialContextFactory
然后用 MC4J 调用 hello(),得到的控制台日志是:
      ClassLoader=[org.apache.geronimo.kernel.config.MultiParentClassLoader id=geronimo/j2ee-security/1.0/car]
      java.lang.ClassNotFoundException: org.openejb.client.RemoteInitialContextFactory
可以看出,两个 ClassLoader 是不相同的!由于 ClassLoader 是由 Thread.currentThread() 得到的,那么,ClassLoader 不同,说明调用它们的线程是不同的。
再来分析 doStart() 和 hello() 的执行过程。doStart() 是在部署时,由 Geronimo 的部署线程来调用的。由于在部署计划中指定了 Dependency,此时的 ClassLoader 中,是包含了 openejb-core-2.0.jar 的,此时能找到类。在调用 hello() 时,MC4J 向 JMXConnector 发出调用命令,而 JMXConnector 将产生 Thread,并设置 Thread 的 ContextClassLoader,此时的 ClassLoader 是 JMXService 的,不是 HelloWorldGBean 自己的。
原来 ClassLoader 是被绑架了!
解决方法
知道了产生问题的原因,解决起来就容易了:移花接木!
在初始化 EJB 之前,加上如下语句:
    ClassLoader clOld = Thread.currentThread().getContextClassLoader();
    ClassLoader clSelf = getClass().getClassLoader();
    Thread.currentThread().setContextClassLoader(clSelf);
在调用完成后,别忘了还原 Thread 的 ClassLoader:
    Thread.currentThread.setContextClassLoader(clOld);
修改后的程序,从 MC4J 中调用,也能找到 EJB 相关的类了。
在网上有一些类似的解决方法,都是建议把所用到的 JAR 包拷贝到容器的 Bootstrap 目录下去,例如 Tomcat/common/lib、JRE/lib、JRE/lib/ext 等。笔者认为,此法不可取,副作用太大。例如:从 Geronimo 中调用 JBoss 的 EJB,该怎么办?如果把 JBoss 的 Jar 文件拷到 Bootstrap 目录中去,Geronimo 就无法启动了。
该例子中,所涉及的源代码比较简单,就不提供下载了。如果您确实需要,请回复本文,留下邮件地址。

本文出自 “豪客城” 博客,转载请与作者联系!

你可能感兴趣的:(ClassLoader,职场,休闲,Geronimo)