在Eclipse插件程序(RCP程序)中使用Spring Remoting的问题一例

我们的项目后台是J2EE应用,跑在IBM WebSphere上,前端由于业务要支持较为复杂的交互,所以没有使用网页,而是用Eclipse RCP富客户端程序。即一种现代C/S架构。后台使用了Spring框架。前端用Spring的remoting调用后端,这是Spring自带的RPC工具,它支持RMI,也支持HTTP(叫做Http Invoker)。

注意区分RCP和RPC:Eclipse RCP是Rich Client Platform,是从Eclipse插件扩展机制发展而来的富客户端(GUI)程序;Spring RPC则是远程调用(Remote Process Call)方案。

Spring的RMI RPC是典型的AOP,使用ProxyFactoryBean生成代理。它拦截客户端的调用,通过RMI协议请求远端服务器,服务端URL和接口都是配置好的,然后接收返回结果给客户端。Bean示例如下:

<bean id="myClient" class="com.travel.rcp.SomeClient" singleton="false">
    <property name="myService">
        <ref bean="myServer"/>
    </property>
</bean>

<bean  id="myServer" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
    <property name="serviceUrl">
        <value>rmi://192.168.40.118:9060/myService</value>
    </property>                   
    <property name="serviceInterface">
        <value>com.travel.rcp.SomeServer</value>
    </property>
</bean>

可是在生成Proxy Bean的过程中出现了初始化错误。而且错误信息极不完整。只好用debug跟入spring的源代码。spring版本用的1.2.7。进入如下方法:

public Object getProxy(ClassLoader classLoader) {
  if (logger.isDebugEnabled()) {
   Class targetClass = this.advised.getTargetSource().getTargetClass();
   logger.debug("Creating JDK dynamic proxy" +
     (targetClass != null ? " for [" + targetClass.getName() + "]" : ""));
  }
  Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
  return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

最后一句Proxy.newProxyInstance(classLoader, proxiedInterfaces, this)出现了初始化错误。

但仍然不知道错在哪里。单独写一个简单程序,使用同样的配置和接口,则不会出现错误。

简单程序与Eclipse插件系统的区别在于classloader,因此怀疑问题出在classloader上。

打印classloader后发现,在eclipse里,classloader并不遵循JDK的bootstrap - extension - application三层结构,它只有两层,这两层跟底下JDK的classloader还是隔离的。而spring用的是JDK的classloader。也就是说,spring类的classloader和eclipse类的classloader隔离了。

spring之所以用JDK的classloader是因为用了JDK的动态代理方式。因此考虑使用CGLIB来代替。办法是修改spring源码,在下面这个方法里:

public AopProxy createAopProxy(AdvisedSupport advisedSupport) throws AopConfigException {
  if (advisedSupport.isOptimize() || advisedSupport.isProxyTargetClass() ||
      advisedSupport.getProxiedInterfaces().length == 0) {
   if (!cglibAvailable) {
    throw new AopConfigException(
      "Cannot proxy target class because CGLIB2 is not available. " +
      "Add CGLIB to the class path or specify proxy interfaces.");
   }
   return CglibProxyFactory.createCglibProxy(advisedSupport);
  }
  else {
   return new JdkDynamicAopProxy(advisedSupport);
  }
}

争取让代码走上面那个分支,不走return new JdkDynamicAopProxy。

于是在org.springframework.aop.framework.ProxyFactory.getProxy(Class proxyInterface, Interceptor interceptor)里将isOptimize设为true。按说这样就没问题了,可是运行的时候报了另一个错:

Either an interface or a target is required for proxy creation

跟踪进org.springframework.aop.framework.Cglib2AopProxy中,发现如下代码:

//DK - is this check really necessary?
//should this 'config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE' be enough?

if (config.getTargetSource().getTargetClass() == null) {
 throw new AopConfigException("Either an interface or a target is required for proxy creation");
}

看这个注释,原来这个 if 检查是可以不要的,干脆去掉。然后再运行,又报错。这次错误发生在方法public Object getProxy(ClassLoader classLoader)里面:

Class rootClass = this.advised.getTargetSource().getTargetClass();

又是TargetSource。考虑一下,所谓的TargetSource无非是要代理的那个原始对象,也就是我们的com.travel.rcp.SomeServer接口,所以干脆改成

Class rootClass = this.advised.getProxiedInterfaces()[0]

这样一来,程序可以正确的运行了。

但是我们这样狂改一通,带来的缺陷也是显而易见的。第一,spring源码改了,不好维护了,以后升级版本怎么办。第二,破坏了spring AOP proxy的逻辑,我们为了绕过一个问题而改用CGLIB,为了绕过另一个问题把target class替换成了proxied interface;按说,用CGLIB就用target class,用JDK动态代理就用proxied interface才对。

最终解决:

为了最终解决这个问题,又阅读了一些spring的资料,发现强制使用CGLIB不用改源代码,只要把proxyTargetClass设为true。而TargetSource也是可以配置的,还支持热插拔(HotSwappableTargetSource)功能;但还是需要改一部分代码。

最终,发现一个非常简单的解决方案,即spring用JDK动态代理时的classloader是通过Thread.getContextClassLoader()获得的。因此只要在我们的Eclipse RCP客户端程序里写一句

Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());

把classloader替换成Eclipse的,这样解决了JDK classloader和Eclipse classloader隔离的问题,也就解决了一切。真是踏破铁鞋无觅处得来全不费工夫。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值