第33章 Spring远程方案(三)

第33章 Spring远程方案

33.3 Spring Remoting提供的远程服务支持

33.3.3 基于Web服务的远程方案

Spring Remoting从最初就对JAX-RPC标准的We服务提供支持。顺应JAX-RPC的后继者JAX-WS后浪推前浪之势,Spring在2.5发布之后,又添加了对基于JAX-WS标准的Web服务的相应支持。但不管采用什么样的We服务标准,在Spring中的访问风格基本都是一致的,依然是抽象概念上的Service Exporter和Service Accessor,只不过具体的实现细节不同而已。

1. 通过Web服务公开远程服务

如果说Spring为其他远程方案提供的Service Exporter和Service Accessor实现都是均衡发展的话,那么,对Web服务的支持就有些“偏袒”的意味了。在使用Web服务以远程方式公开本地服务方面,Spring并没有像其他远程方案那样,为其提供完备的Service Exporter实现,除了一个简单的ServletEndpointSupport可供我们驱使之外,标准的Spring框架内再没有相关的更多支持。出现这种状况的原因如下所述。

  • 要实现一套完备的Web服务开发框架需要付出很多的努力。在Spring Remoting框架内实现一套完备的Web服务开发框架,显然已经超出了Spring Remoting只是希望提供轻量级远程方案的本意。不过,虽然不能将一套完备的We服务开发框架直接放到Spring框架内发布(可能带来开发、管理和维护上的问题),但是,却完全可以使用Spring框架的理念单独在次一级的项目中开发一套Web服务框架。属于Spring框架子项目的Spring Web Services(http://static.springframe-work.org/spring-ws/site/)恰好就提供了这么一套Web服务开发框架。

  • 现在已经存在许多成熟的Web服务开发框架,除了Spring Web Services项目,还有Apache的Axis/Axis2、Codehaus的xFire(http://xfire.codehaus.org/),以及现在合并了xFire和Celtix孵化完成的Apache CXF(http://cxf.apache.org/)项目。但各种Web服务开发框架的实现,在公开具体服务的时候都有特定于自己的方式,所以,看来Spring框架在统一这一问题的处理方面确实也有些无能力为了。

虽然Spring没有为以Web服务形式远程公开相应服务提供太多支持,但我们通常可以使用相应Web服务开发框架提供的各种手段,尤其像xFire和CXF,它们与Spring框架可以很好地集成。我们不妨以CXF为例,看一下它是如何将本地业务对象以Web服务的形式公开出去的。

注意:org.springframework.remoting.jaxrpc.ServletEndpointSupport为我们实现JAX-RPC中Servlet类型的Endpoint提供了便利支持,可以让我们在实现类中获取Spring的ApplicationContext容器的支持。如果需要的话,可以扩展该类实现自己的Servlet类型EndPoint。Spring参考文档中就ServletEndpointSupport的使用提供了简短的介绍。

要借助于CXF将ITTMRateService以web服务的形式公开为远程服务,我们可以使用CXF提供的org.apache.cxf.jaxws.JaxWsServerFactoryBean实现类。在将我们的ITTMRateService以及相关类如下所示稍作手脚之后:

@WebService
public interface ITIMRateService {
    
    
  List<TTMRate> getTMRatesByTradeDate(TradeDate tradeDate);
}

我们就可以通过向服务端的ApplicationContext中添加下方代码清单所示的配置内容,将ITTMRateService以Web服务的形式公开出去。

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd.
http://cxf.apache.org/jaxwshttp:1/cxf.apache.org/schemas/jaxws.xsd">

  <import resource="classpath:META-INF/cxf/cxf.xml"/>
	<import resource="classpath:META--INF/cxf/cxf-extension-soap.xml"/>
	<import resource="classpath:META-INF/cxf/cxf-servlet.xml."/>

  <jaxws:endpoint id="ttmServiceViaCXF" address="/ttmServiceViaCXF" implementor="#ttmRateService">
	</jaxws:endpoint>
	
  <beanid="ttmRateService" class="..service.MockTTMRateService">
	</bean>
  ...
</beans>

<jaxws:enapoint>的背后是JaxWsServerFactoryBean在支撑,而JaxWsServerFactoryBean在这里实际上就是我们的Service Exporter,所以,CXF从总体理念上都是与Spring的Remoting理念保持看齐的。

注意:实际上,要让我们的ITTMRateService真正以Web服务公开出去,我们还有部分细节需生要处理。按照CXF的配置要求,如果要通过HTTP形式将当前服务公开出去的话,我们需要向web.xml添加CXFServlet相关定义,并让Spring的ContextLoaderlistener加载我们上面的ApplicationContext配置文件,具体细节请参考CXF配置文档。

2. 通过Web服务访问远程服务

要访问刚才通过CXF公开出来的Web服务,最直接的方式就是使用CXF同时提供的ServiceAccessor实现,即org.apache.cxf.jaxws.JaxwsProxyFactoryBean,而且,在CXF提供了自定义命名空间的情况下,我们可以很容易地在客户端的ApplicationContext配置文件中添加该ServiceAccessor的定义并使用,如下方代码清单所示。

<beans xmlns="http://www.springframework.org/schema/beans" wxmlns:xsi="http://www.w3.org/2001/XMLSchemainstance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans2.5.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
  
  <import resource="classpath:META-INF/cxf/cxf.xml"/>
  <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml"/>
  <import resource="classpath:META-INF/cxf/cxf-servlet.xmnl"/>	
  
  <bean id="client" class="..TTMServiceClient">		
    <property name="ttmService" ref="ttmServiceViaCXF"/>
  </bean>
  	
  <jaxws:client id="ttmServiceViaCXF" address="http://yourhost:8080/application/cxf/ttmServiceViaCXF" serviceClass="..ITTMRateService">
	</jaxws:client>
</beans>

我想,<jaxws:client>背后的奥秘现在看来应该不难理解吧?除了使用像CXF之类Web服务框架提供的特定的Service Accessor实现来访问相应远程服务之外,我们还可以使用Spring Remoting专门为JAX-RPC和JAX-WS类型Web服务访问提供的“制式装备”,详情如表33-1所列的。

image-20220722204543480

对于同样的ITTMRateService远程服务,我们可以选择使用JaxWsPortProxyFactoryBean完成同样的访问。不过这次看起来好像没有CXF“量身定做”的ServiceAccessor那么合身罢了,如下方代码清单所示。

<bean id="client" class="..TTMServiceClient">
	<property name="ttmService" ref="ttmServiceViaCXF"/>
</bean>

<bean id="ttmServiceViaCXF" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
	<property name="serviceInterface" value="..ITTMRateService"/>
	<property name="wsdlDocumentUrl" value="http://localhost:8080/simplefx/cxf/ttmServiceч
ViaCXF?WSDL"/>
	<property name="namespaceUri" value="http://service.simplefx.spring21.cn/"/>
	<property name="serviceName" value="MockTTMRateServiceService"/>
	<property name="portName" value="MockTTMRateServicePort"/>
</bean>

JaxWsPortProxyFactoryBean定义的各种属性,比如namespaceUrl、serviceName和poftName分别对应.wsdl文件中的targetNamespace、服务名称以及端口名称。我想,通过wsdlDocumentUrl访问到.wsdl文件之后,这些信息不难找到(上方代码清单中是我的本地应用对应的各种属性值)。

注意:俗话说得好,“功夫在诗外”,要发挥Spring Remoting提供的这些Web服务相关的ServiceAccessor实现类的功效,除了参考相应的Javadoc之外,我想,我们不得不首先去理解JAX-RPC和JAX-WS以及其他Web服务规范吧?

目前为止,我们说的其实都是一个问题,那就是,不管我们使用的ServiceAccessor实现类怎么变换,客户端调用代码却可以“我自岿然不动”,唯一变动的只是简单的配置变换而已。我想SpringRemoting的ServiceAccessor的魅力就在这里。所以,如果某些情况下,Spring Remoting没有为我们提供相应的Service Accessor实现的话,就算我们“依葫芦画瓢”开发一个也不是什么难事吧?当然,如果大多数远程方案都能够像CXF那样,事先提供现成的Service Accessor实现类,那就实在太好了。

33.3.4 基于JMS的远程方案

要说Spring中基于JMS的远程方案,那一定要提到Codehaus的Lingo(http://ingo.codehaus.org/Home)。早在Spring2.5发布了对基于JMS的远程方案的官方支持之前,Lingo就已经活跃在战斗第一线了。Lingo的远程消息架构完全遵循Spring Remoting的Service Exporter和Service Accessor统一实现风格,而且,提供了对各种远程消息模式的支持。即使是在Spring2.5引入了官方标准JMS远程方案之后,Lingo依然是值得我们关注的。当然,既然我们主要关注的是Spring Remoting标准方案,下面我们还是要围绕着Spring Remoting提供的基于JMS远程方案进行阐述。现在让我们开始吧!

注意:Lingo拥有许多Spring Remoting标准JMS远程方案所没有的特性,你可以通过http://ingo.codehaus.org/Home以获取Lingo的更多信息,“工欲善其事,必先利其器”,如果Spring Remoting提供的标准JMS远程方案无法满足我们的当前场景需求,不妨转而拜访Lingo试试。

1. 基于JMS的远程方案实现

分析在我们已经了解了Spring对JMS的集成相关内容之后,要理解Spring Remoting提供的基于JMS的远程方案也就要简单多了。我们开门见山吧!

在JMS场景中,两个不同的对象或者组件需要相互通信,基本上都是通过指定的Destination作为“桥梁”。在同步场景中,客户端对象发送消息到指定的Destination,服务端对象即可从同样的Destination中接收客户端发送的消息进行处理。如果处理需要返回结果,那么服务端对象通常将结果以JMS消息的形式发送到请求消息的JMSReplyTo指定的Destination中。这样,客户端即可从其当初为请求消息设置的JMSReplyTo对应的Destination中获取处理结果消息。实际上,Spring Remoting提供的基于JMS的远程方案运作方式与此基本相同,只不过,发送的消息形式是固定的。在通常的同步场景中,我们或许可以发送任何希望的消息类型,而在Spring Remoting的基于JMS的远程方案中,我们发送的消息类型为对应RemoteInvocation类型的ObjectMessage。

在客户端通过指定的服务接口调用相应的业务方法之后,对应的Service Accessor将通过MethodInterceptor对服务接口上的业务方法调用进行拦截,然后将调用信息(通常是方法名和相应的方法参数)以RemoteInvocation进行封装,并创建为ObjectMessage以发送。但是,发送请求消息之前,Service Accessor有一件事情要做,那就是创建一个TemporaryQueue以作为调用结果返回的Destination。在TemporaryQueue创建之后,把它设置为要发送的ObjectMessage的JMSReplyTo就可以开始发送了。

公开远程服务的Service Exporter将从与Service Accessor发送消息使用的同一个Destination中接收请求消息,然后对实际消息类型为RemoteInvocation的ObjectMessage进行解码,提取RemoteInvocation中的调用信息,并通过反射调用本地业务对象对应的业务方法。在调用完成之后,ServiceExporter将同样对调用结果进行封装(封装类为RemoteInvocationResult),最后,ServiceExporter会将封装后的调用结果消息发送到请求消息的JMSReplyTo指定的Destination,我们在发生消息的时候已经创建它了。

在Service Accessor以同步的形式从其最初创建的TemporaryQueue中接收到调用结果消息之后,即可把消息解码然后返回给客户端调用对象,至此,整个过程即告完成。

我们可以使用图33-7来进一步描述整个远程服务的公开和访问过程。

image-20220722210004881

实际上,图33-7所展示的情景跟我们使用TemporaryQueue完成JMS同步消息发送和接收的结构图没有什么差别,不是吗?

2. 通过JMS公开和访问远程服务

在了解了Spring Remoting中基于JMS的远程方案实现原理之后,要运用就更加轻松了。在开始正式工作之前,我们先设置一些客户端和服务端都要使用的共同设施,比如ConnectionFactory和二者共同使用的Destination。我们将这些公用设施放到一个单独的SpringIoC容器配置文件中,如下所示:

#fms-remoting-commong.xml
<util:properties id="indiEnv" location="classpath:amq_jndi.properties"/>
<jee:jndi-lookup id="connectionFactory" jndi-name="ConnectionFactory" environment-ref="jndiEnv"/>
                                                                                               <jee:jndi-lookup id="remotingQueue" jndi-name="dynamicQueues/remoting" environment-ref="jndiEnv"/>

我们这里依然使用ActiveMQ,但并不是必要的,你也可以选择其他的JMS Provider实现,这完全要根据实际情况来决定。

在有了基础设施之后,我们开始通过JMS公开我们的ITTMRateService。在基于JMS的远程方案中,对应的Service Exporter实现是org.springframework.jms.remoting.JmsInvokerServiceExporter。它实际上是一个消息驱动POJO,因为它实现了SessionAwareMessageListener接口(希望你还记得它与MessageListener的区别)。所以,要使用JmsInvokerServiceExporter公开ITTMRateService服务,看来只需要将它挂接到相应的MessageListenerContainer就好,如下方代码清单所示。

#server.xml
<bean id="ttmRateService" class="..MockTTMRateService">
</bean>

<bean id="ttmServiceExporter" class="org.springframework.jms.remoting.JmsInvokerServiceExporter">
	<property name="serviceInterface" value="..ITTMRateService"/>
	<property name="service" ref="ttmRateService"/>
</bean>

<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
	<property name="connectionFactory" ref="connectionFactory"/>
	<property name="destination" ref="remotingQueue"/>
	<property name="concurrentConsumers" value="3"/>
	<property name="messageListener" ref="ttmServiceExporter"/>
</bean>

现在,JmsInvokerServiceExporter可以迎接任何对ttmRateService的调用请求了。

小心:我们使用通常的bean定义形式来使用DefaultMessageListnerContainer或者SimpleMessageListenerContainer,而没有使用新添加的jms命名空间下的<jms:listenercontainer><jms:listener>。因为这会导致服务端接收不到调用消息,具体原因我也不清楚,猜测可能是<jms:listener-container><jms:listener>的handler有部分细节没有处理造成的。

若要访问基于JMS的远程服务,我们使用的Service Accessor实现类为org.springframework.jms.remoting.JmsInvokerProxyFactoryBean,它既然要发送消息到指定的Destination,自然需要一些必要的装备设置,所以,通常提供如下方代码清单所示的配置之后,JmsInvokerProxyFactoryBean就可以将合适的ITTMRateService代理对象注入给客户端对象了。

# client.xm1
<bean id="client" class="..TTMServiceClient">
	<property name="ttmService" ref="ttmServiceViaJms"/>
</bean>

<bean id="ttmServiceViaJms" class="org.springframework.jms.remoting.JmsInvokerProxyFactoryBean">
	<property name="serviceInterface" value="..ITTMRateService"/>
	<property name="connectionFactory" ref="connectionFactory"/>
	<property name="queue" ref="remotingQueue"/>
</bean>

虽然我们数次替换掉了TTMServiceClient所使用的ITTMRateService,但“可怜却又如此舒服”的TMServiceClient依然对于自己使用的ITMRateService实现类在哪里是浑然未知,幸哉!

猜你喜欢

转载自blog.csdn.net/qq_34626094/article/details/125957313
今日推荐