关于远程调用(XFire/HttpInvoker/Hessian etc.)及远程服务管理的一些随想

关于远程调用(XFire/HttpInvoker/Hessian etc.)及远程服务管理的一些随想
 
在现代 J2EE 企业应用系统中,存在着 Hessian HttpInvoker XFire Axis 等多种形式的远程调用技术。尽管有 Spring 等框架对这些技术进行了封装,降低了使用的复杂度,但对普通程序员而言仍是复杂的——至少需要要掌握这些技术的基础知识。
 
无论使用那种技术,其基本原理都是一样的:服务端生成骨架,对外暴露服务;客户端生成服务代理,访问调用服务。通常情况下,生成服务代理的代价比较高昂,这也是我们第一次访问远程服务速度比较慢的原因,为每个请求生成新的服务代理恐怕不是我们所期望的。更何况,如果采用这种方式,就要在代码里针对各种不同的技术(如 XFire HttpInvoker )编写不同的服务生成和调用的处理代码。不仅麻烦,而且容易出错。我想,没有人愿意去直接操作各种框架技术的底层代码,这并不是一个好注意!
 
作为一种替代方案,我们设计了一个“服务池”的功能,或者说“服务工厂”更贴切一点。先看下面这张类图:
 
如上图所示,针对 HttpInvoker XFire Hessian 等各种远程调用技术,抽象出一个“远程服务池”(服务工厂)既 RemoteServicePool 接口。该接口提供了获取服务及一些其他的辅助功能,并针对 HttpInvoker XFire Hessian 等不同技术提供了相应的具体实现。采用这种方式,开发人员只需在代码中“注入” RemoteServicePool ,并以统一的方式(如 getService() )获取实际的服务,只是针对不同技术在配置上有些须差异而已。该技术的原理非常简单,在应用启动之前把所有存在的服务提供者提供的服务都配置好,并为它们分配一个唯一的 ID 。应用启动之后,框架会自动生成和这些地址相对应的服务代理( ServiceProxy ),这些代理已经是可用的服务,服务获取的细节被完全屏蔽掉,开发者只要知道如何从 RemoteServicePool 中获取服务就可以了。看一下服务池的接口定义:
/**
  * 远程服务缓冲池。
  *
  * <p>
  * 对于一个既定的服务契约(既接口),可以有很多服务提供者( <b> ServiceProvider </b> )。该接口的提出,是为了解决服务访问者和服务提供者之间 一对多 的关系。
  *
  * @author Tony
  */
public interface RemoteServicePool {
    /**
     * 从缓冲池中获取一个 <b> Service </b>
     *
     * <p>
     * 此处获得的是一个 <code> Object </code> ,需要调用者自己做类型转换。
     *
     * <p>
     * 参数 <b> serviceId </b> 代表服务缓冲池中的一个实例名称。服务类型采用配置文件中默认的类型。
     *
     * @param serviceId
     *             实例名称
     *
     * @return 服务对象
     */
    Object getService(String serviceId);
}
 
接下来看看如何配置服务:
    <bean id="userServicePool" class="com. tonysoft .common.XFireRemoteServicePool">
       <property name="serviceInterface">
           <value>com. tonysoft .demo.service.UserService</value>
       </property>
       <property name="serviceUrls">
           <map>
              <entry key=" server 1 ">
                  <value>http://localhost:8080/server1/service/userService?WSDL</value>
              </entry>
              <entry key="server2">
                  <value>http://localhost:8080/server2/service/userService?WSDL</value>
              </entry>
           </map>
       </property>
    </bean>
最后再来看一下访问服务的代码:
  /** 服务工厂 */
    public RemoteServicePool userServicePool ;
    /**
     * 测试新增一个不存在的用户。
     */
public void testAddUser() {
UserService       userService = null ;
        try {
            userService =(UserService) userServicePool .getService("server2");
        } catch (Exception e){
            throw new RuntimeException( " 获取服务失败,失败原因:" + e);
        }
        OperateResult result = userService .addUser( new User( "daodao" , " 技术部" ));
        assertEquals(result.isSuccess(), true );
    }
 
 
该方案还为“双向关联”的系统服务提供了一个很好解决办法。看下面一张图:

如图,系统 B 和系统 C 都调用系统 A 进行付款操作;同时系统 A 要用远程服务向系统 B 或系统 C 进行认证操作,认证操作的接口(契约)都是一样的,业务逻辑可能有所差异。在这种情况下,配置在系统 A 中的认证服务就比较麻烦,因为要根据不同的系统调用认证服务,既从 B 过来的请求要访问 B 的认证服务,从 C 过来的请求要访问 C 的认证服务。用服务池可以很好的解决这个问题,把两个系统( B C )提供的认证服务地址都配置在同一个服务池中,根据不同的 ID (如 B C )来决定使用那个系统的服务。
 
       尽管服务池解决了一些问题,在某种程度上降低了复杂度,但仍存在如下一些问题:
*         服务的运行期动态注册
*         服务的自动注入( IoC
*         透明化服务 ID 的传递
 
在服务池( ServicePool )概念的基础上进行扩展,我们得出了如下的系统模型:

在核心位置上是一个服务中心资源库( ServiceRepository ),存储了系统中用到的所有的远程服务。服务采取动态注册的机制,由对外提供的服务注册器( ServiceRegister )提供服务注册功能。外部系统可以实现该接口向资源中心注册服务。提供了一个启动时运行的注册器,可以把静态配置在系统中的服务都注册进来。
 
       服务的生成、管理等均由服务中心自己维护,委托服务代理生成器( ServiceProxyGenerator )完成服务的创建。可以针对现有的远程调用方式,如 XFire,HttpInvoker,Hessian 等创建服务代理,也可以针对自己定义的远程调用方式创建服务代理,由 CustomServiceProxyGenerator 完成该功能。
       一个服务模型包括 5 个因素:
*         服务接口 serviceClass
*         服务 ID serviceId
*         服务类型 serviceType
*         服务地址 serviceUrl
*         附加属性 props
查找一个服务需要两个因素,一个是服务接口,另一个是服务 ID 。这两个因素共同决定了一个服务,既服务中心内部的“服务 ID ”。通过这种方式,可以允许存在多个 ID 相同但接口不同的服务,也可以存在多个接口相同但 ID 不同的服务。
 
服务 ID 的获取是系统中一个关键的功能,这部分对程序员来说应该是透明的,由系统自己维护。相应的提供了一个服务 ID 提供者 (ServiceIdProvider) 接口,由实现该接口的子类完成服务 ID 获取功能(这是比较关键的地方,需要特殊考虑)。
 
对于程序员来说,使用服务中心里的服务再也不能比这样再简单了!看看配置:
    < bean id = "helloHttpInvokerService" parent = "abstractServiceProxyFactory" >
       < property name = "serviceInterface" >
           < value > com.tonysoft.common.service.repository.example.HelloHttpInvoker </ value >
       </ property >
    </ bean >
再看如何使用这个 bean
private HelloHttpInvoker       helloHttpInvokerService ;
 
public void testHttpInvoker() {
        assertNotNull( "helloHttpInvokerService can't be null !" , helloHttpInvokerService );
        assertEquals ( "Hello , HttpInvoker !" , helloHttpInvokerService .sayHello());
}
 
    /**
     * @param helloHttpInvokerService
     *             the helloHttpInvokerService to set
     */
    public void setHelloHttpInvokerService(HelloHttpInvoker helloHttpInvokerService) {
        this . helloHttpInvokerService = helloHttpInvokerService;
    }
 
就是这样的简单! Spring 会把这个 bean 自动注入到程序中,可以象使用其他任何 bean 一样使用它!程序员完全不用关心该服务由谁提供、采用什么技术,他只要知道系统中存在这样一个服务就 OK 了。该技术彻底向程序员屏蔽了底层技术的实现细节,以统一的方式访问任何形式的远程服务。至于服务是如何生成、如何配置的将在后面叙述。
 
服务( Service Bean )是如何实现自动注入( IoC )的呢?
注意到上面配置的 bean 都继承了“ abstractServiceProxyFactory ”,它是一个工厂 bean ,负责根据给定的接口类型,到服务中心( ServiceRepository )查找服务,并生成服务代理。我们来看一下它的核心代码:
/**
  * 服务代理工厂。
  *
  * <p>
  * 该工厂对程序员屏蔽了服务实现的技术细节,对于 XFire Hessian HttpInvoker 等常用远程服务形式进行封装。
  *
  * <p>
  * 程序员只需要提供一个服务接口(契约),该工厂会从服务中心 <code> ServiceRepository </code> 中查找符合该接口的远程服务实例。
  *
  * <p>
  * 查找的规则是由服务 ID 提供者所提供的服务 ID 和服务接口名字共同组成的服务关键字匹配。
  *
  * @author Tony
  */
public class ServiceProxyFactory implements FactoryBean {
 
    /** 服务中心 */
    private ServiceRepository serviceRepository ;
 
    /** 服务 ID 提供者 */
    private ServiceIdProvider serviceIdProvider ;
 
    /** 服务接口 */
    private Class             serviceInterface ;
 
    /*
     * @see org.springframework.beans.factory.FactoryBean#getObject()
     */
    public Object getObject() throws Exception {
        return ProxyFactory.getProxy(getObjectType(), new ServiceProxyInterceptor());
//        return serviceRepository.getService(serviceInterface, serviceIdProvider.getCurrentServiceId());
    }
 
    /*
     * @see org.springframework.beans.factory.FactoryBean#getObjectType()
     */
    public Class getObjectType() {
        return serviceInterface ;
    }
 
    /*
     * @see org.springframework.beans.factory.FactoryBean#isSingleton()
     */
    public boolean isSingleton() {
        return true ;
    }
 
    /*
     * 远程服务代理拦截器。
     */
    private class ServiceProxyInterceptor implements MethodInterceptor {
 
        /*
         * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
         */
        public Object invoke(MethodInvocation invocation) throws Throwable {
 
            Method method = invocation.getMethod();
            Object[] args = invocation.getArguments();
            Object client = getClient();
            return method.invoke(client, args);
        }
 
        private Object getClient() {
            try {
                 return serviceRepository .getService( serviceInterface , serviceIdProvider .getCurrentServiceId());
            } catch (ServiceException e) {
                // TODO
                e.printStackTrace();
                return null ;
            }
        }
 
    }
 
    // ---- 容器自动注入 ----
    ······
我们注意上面加粗、倾斜的那段代码:
return serviceRepository .getService( serviceInterface , serviceIdProvider .getCurrentServiceId());
真正的魅力就在这个地方。根据服务接口类型和服务 ID ,从服务中心获取特定的服务。服务接口是配置好的, 而服务 ID 则在运行时才能确定,根据不同的应用、不同的策略提供不同的 ServiceIdProvider 。其中用到了 Spring FactoryBean 和拦截器,至于为什么要在这里使用拦截器,可以参考 Spring 框架的源码。
 
服务代理生成器( ServiceProxyGenerator )也是一个值得一提的地方,我们先看一下它的接口:
/**
  * 服务代理生成器。
  *
  * @author Tony
  */
public interface ServiceProxyGenerator {
 
    /**
     * 取得服务代理对象。
     *
     * @param serviceClass
     *             服务接口
     * @param serviceUrl
     *             服务地址
     * @param props
     *             附加属性
     * @return 代理对象
     * @throws Exception
     */
    Object getService(Class serviceClass, String serviceUrl, Properties props) throws Exception;
}
它只有一个 getService() 方法,那么为什么设计这个接口?在什么地方使用呢?回答这个问题之前先来看看下面这段代码:
    public void registService(ServiceModel serviceModel) throws ServiceException {
        ······
 
        String key = serviceModel.getServiceId() + KEY_SPAR
                     + serviceModel.getServiceClass().getName();
 
        if ( serviceNames .contains(key)) {
            throw new ServiceRegistException( "service is exist!" );
        }
 
        Object proxy = null ;
 
        try {
            ServiceProxyGenerator proxyGenerator = (ServiceProxyGenerator) beanFactory
                .getBean(serviceModel.getServiceType() + PROXY_GENERATOR_END );
 
            proxy = proxyGenerator.getService(serviceModel.getServiceClass(), serviceModel
                .getServiceUrl(), serviceModel.getProps());
        } catch (Exception e) {
            throw new ServiceRegistException( "can't regist service !" , e);
        }
 
        if (proxy != null ) {
            serviceNames .add(key);
            serviceContainer .put(key, proxy);
        } else {
            throw new ServiceRegistException( "fail to regist service !" );
        }
    }
上面做特殊标记的代码就是应用服务代理生成器的地方,这里我们用到了 Spring bean 工厂,根据注册服务的类型( xfire,httpinvoker,hessian 等)到 Spring 容器里查找相应的生成器,并生成指定类型的服务。看下面配置的几个服务代理生成器:
    <!-- XFire 类型服务代理生成器 -->
    < bean id = "xfire_generator" class = "com.tonysoft.common.service.repository.generator.XFireServiceProxyGenerator" lazy-init = "true" >
       < property name = "serviceFactory" >
           < ref bean = "xfire.serviceFactory" />
       </ property >
    </ bean >
   
    <!-- Hessian 类型服务代理生成器 -->
    < bean id = "hessian_generator" class = "com.tonysoft.common.service.repository.generator.HessianServiceProxyGenerator" lazy-init = "true" >
    </ bean >
 
    <!-- HttpInvoker 类型服务代理生成器 -->
    < bean id = "httpinvoker_generator" class = "com.tonysoft.common.service.repository.generator.HttpInvokeServiceProxyGenerator" lazy-init = "true" >
    </ bean >
   
    <!-- 自定义 类型服务代理生成器 -->
    < bean id = "custom_generator" class = "com.tonysoft.common.service.repository.generator.CustomServiceProxyGenerator" lazy-init = "true" >
    </ bean >
   
    <!-- 服务中心(资源库) -->
    < bean id = "serviceRepository" class = "com.tonysoft.common.service.repository.DefaultServiceRepository" >
    </ bean >
   
    <!-- 服务 ID 提供者 -->
    < bean id = "serviceIdProvider" class = "com.tonysoft.common.service.repository.provider.DefaultServiceIdProvider" >
    </ bean >
   
    <!-- 所有远程服务的基础类 -->
    < bean id = "abstractServiceProxyFactory" class = "com.tonysoft.common.service.repository.ServiceProxyFactory" abstract = "true" >
    </ bean >
       简单看一下 HttpInvoker 类型服务代理生成器的代码:
      
public class HttpInvokeServiceProxyGenerator implements ServiceProxyGenerator {
 
    /** HttpInvoker 服务代理工厂 */
    private HttpInvokerProxyFactoryBean httpInvokerFactory = new HttpInvokerProxyFactoryBean();
 
    /*
     * @see com.alipay.xfiredemo.common.ServiceProxyGenerator#getService(java.lang.Class, java.lang.String,
     *      java.util.Properties)
     */
    public Object getService(Class serviceClass, String serviceUrl, Properties props) {
 
        // Todo initial httpInvokerFactory with props
 
        httpInvokerFactory .setServiceInterface(serviceClass);
        httpInvokerFactory .setServiceUrl(serviceUrl);
 
        // must invoke this method
        httpInvokerFactory .afterPropertiesSet();
 
        return httpInvokerFactory .getObject();
    }
 
}
  是的,正如你所看到的一样,我们这里把真正生成服务代理的任务交给了 Spring HttpInvokerProxyFactoryBean 来完成。
 
提供在初始化时注册的静态服务功能,配制如下:
 
    <!-- 初始化时注册的静态服务 -->
    < bean id = "bootupServiceRegister" class = "com.tonysoft.common.service.repository.register.BootupServiceRegister" lazy-init = "false" >
       < property name = "services" >
           < list >
              <!--
              <bean class="com.tonysoft.common.service.repository.ServiceModel">
                  <property name="serviceClass"><value>com.tonysoft.common.service.repository.example.HelloHessian</value></property>
                  <property name="serviceId"><value>default</value></property>
                  <property name="serviceType"><value>hessian</value></property>
                  <property name="serviceUrl"><value>http://localhost:8080/serviceRepositoryApplication/service/hessian/helloHessian.service</value></property>
                  <property name="props">
                     <props></props>
                  </property>
              </bean>
              -->
              < bean class = "com.tonysoft.common.service.repository.ServiceModel" >
                  < property name = "serviceClass" >< value > com.tonysoft.common.service.repository.example.HelloHttpInvoker </ value ></ property >
                  < property name = "serviceId" >< value > default </ value ></ property >
                  < property name = "serviceType" >< value > httpinvoker </ value ></ property >
                  < property name = "serviceUrl" >< value > http://localhost:8080/serviceRepositoryApplication/service/httpInvoker/helloHttpInvoker.service </ value ></ property >
                  < property name = "props" >
                     < props ></ props >
                  </ property >
              </ bean >
              < bean class = "com.tonysoft.common.service.repository.ServiceModel" >
                  < property name = "serviceClass" >< value > com.tonysoft.common.service.repository.example.HelloXFire </ value ></ property >
                  < property name = "serviceId" >< value > default </ value ></ property >
                  < property name = "serviceType" >< value > xfire </ value ></ property >
                  < property name = "serviceUrl" >< value > http://localhost:8080/serviceRepositoryApplication/service/xfire/helloXFire.service?WSDL </ value ></ property >
                  < property name = "props" >
                     < props ></ props >
                  </ property >
              </ bean >    
           </ list >
       </ property >
    </ bean >
 
具体内容可以参看附件中的资源:
一、 ServiceRepository 的源代码( Eclipse 工程)
二、 一个示例应用
三、 打包部署的 ANT 脚本
 
把项目导入 Eclipse 中,直接运行 Ant 脚本,在 target 目录下会生成服务中心的 jar 包,同时生成示例应用的 war 包,把 war 包放到任意服务器( Server )上并启动服务器并确保应用正常启动。 运行 ServiceRepositoryTest .java 执行完整的单元测试,观测结果。其他的自己看源码吧。
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值