Axis2 Session管理

WebService给人最直观的感觉就是由一个个方法组成,并在客户端通过SOAP协议调用这些方法。这些方法可能有返回值,也可能没有返回值。虽然这样可以完成一些工具,但这些被调用的方法是孤立的,当一个方法被调用后,在其他的方法中无法获得这个方法调用后的状态,也就是说无法保留状态。

读者可以想象,这对于一个完整的应用程序,无法保留状态,就意味着只依靠WebService很难完成全部的工作。例如,一个完整的应用系统都需要进行登录,这在Web应用中使用Session来保存用户登录状态,而如果用WebService的方法来进行登录处理,无法保存登录状态是非常令人尴尬的。当然,这也可以通过其他的方法来解决,如在服务端使用static变量来保存用户状态,并发送一个id到客户端,通过在服务端和客户端传递这个id来取得相应的用户状态。这非常类似于Web应用中通过SessionCookie来管理用户状态。但这就需要由开发人员做很多工作,不过幸好Axis2为我们提供了WebService状态管理的功能。

使用Axis2来管理WebService的状态基本上对于开发人员是透明的。在WebService类需要使用org.apache.axis2.context.MessageContextorg.apache.axis2.context.ServiceContext类来保存与获得保存在服务端的状态信息,这些对象使用HttpSession接口的getAttributesetAttribute方法获得与设置Session域属性。

除此之外,还需要修改services.xml文件的内容,为<service>元素加一个scope属性,该属性有四个可取的值:Application, SOAPSession, TransportSession, Request,不过要注意一下,虽然Axis2的官方文档将这四个值的单词首字母和缩写字母都写成了大写,但经笔者测试,必须全部小写才有效,也就是这四个值应为:applicationsoapsessiontransportsessionrequest,其中requestscope属性的默认值。读者可以选择使用transportsessionapplication分别实现同一个WebService类和跨WebService类的会话管理。

Web服务有着很大的需 求,很多人进入了Web服务这一领域,其结果是人们需要Web服务具有更多的特性,以便使用Web服务可以完成任何事情。但Web服务从设计上来说是没有 状态的。在Web服务世界起初的时候是没有管理会话的概念的,但是现在形势改变了:没有会话管理,开发者是无法开发出高级的应用程序的。一个很好的例子是 银行系统,您登录进入系统,然后取钱,最后退出登录。
    如果我们把这一场景映射到Web服务:
    首先,用户登录进自己的账户(调用登录方法)
    用户取钱(在他的账户上调用一些操作) 
    用户退出登录(完成事务)
    您可以很容易的看出上述3个操作是相互关联的,同一个用户进行3个调用操作。所以,这意味着需要有地方来跟踪用户,另外有地方在用户调用方法期间跟踪用户数据。这也就暗含了需要会话管理。
    当开发高级应用程序时,我们必须打破现有的规则;否则,没有人能够开发出真正的Web应用程序。虽然Web服务本质上是无状态的,但是需要在Web服务之上增加一层会话管理的功能。 


    Axis2的无状态本质
    Axis2的优秀设计原则之一是保持逻辑和状态的分离,所以它没有组件需要维护会话,实际上也是这样做的。在Axis2的处理程序中,传输部分,甚至Aixs引擎部分都是无状态的。因此,对Axis2来说多个实例和一个实例是没有区别的。
    作为最佳实践,处理程序或传输部分不应该保持任何实例变量(如果可能会变化的话),因为那样会打破Axis2无状态的本质。所以,如果处理程序或传输部分需要有状态的方式,正确的做法是在需要的时候保存状态和Axis2的状态层次,而不是保持实例变量。

 
    Axis2的会话类型
    像上面提到的,企业级Web服务无法得到认可除非引在Web服务引擎中入了会话概念。换种说法,因为存在不需要维持会话的Web服务,让每种Web服务引擎都支持会话管理这样的说法是不对的。一个很好的例子是Google搜索服务和Google拼写检查服务。
    会话管理的直接影响是占用内存空间的上升和性能的降低,所以您需要一个折中:您的服务是否要支持会话并提供状态。Axis2是定位于企业级Web服务引擎的,所以它必须提供会话管理。
    有几种类型的会话,它们的生命周期是各不相同的。有些会话存在几秒钟,而有些会话的生命周期和系统一样长。Axis2设计为支持4种类型会话,它们存在很多差别,希望您能够使用以下4种会话类型开发出各种Web服务。
    Request
    SOAPSession
    Application
    Transport


    Axis2的上下文层次结构
    在深入讨论Axis2的会话之前,理解Axis2的各种上下文是很有帮助的。如上所述,Axis2保持逻辑和状态的分离,上下文是保存状态的地方。在Axis2中有5种类型的上下文:
ConfigurationContext:整个系统运行时的表述,为了启动Axis2系统需要有一个配置上下文。配置上下文与系统的生命周期是一样的,在这里保存一些状态(配置项)能够一直有效(直到系统关闭)。
ServiceGroupContext:在Axis2中,可以在一个服务组中部署多个服务。在运行时,有一个或多个服务组(依赖于组的会话范围)上下文在会话范围内保存组的状态。
ServiceContext:表述一个服务的运行时状态,其生命周期与会话的生命周期相同。依赖于所对应服务的会话范围,存在一个或多个服务上下文。
OperationContext:表述MEP(Message Exchange Patterns,消息交换格式)的生命周期。操作上下文的生命周期通常短于服务上下文的生命周期。
MessageContext:表述所输入消息的生命周期。如果给定执行链中的两个处理程序想共享数据,最好的方法是把他们存在消息上下文中。一个操作上下文可能有多个消息上下文。
    与会话范围无关,当会话开始和结束时,Axis2会通知服务的实现类,但是需要在服务的实现类中增加两个方法。 

[java] view plain copy
  1. public void init(ServiceContext serviceContext) {   
  2. }   
  3.   
  4. public void destroy(ServiceContext serviceContext) {   
  5. }   

    除此之外,当服务接收到一个请求时,它会传入相应的操作上下文作为参数进行通知。如果服务想访问输入消息的上下文或输入的SOAP消息,可以在服务的实现类中加入以下方法来做到这一点,在真正的方法调用之前调用这一方法。 

[java] view plain copy
  1. public void setOperationContext(OperationContext operationContext) {   
  2. }   

   

    请求会话范围(Request Session Scope)
    请求会话是Axis2默认的会话类型,部署服务时没有指定会话管理的内容,服务会被部署为请求会话类型。这种会话的生命周期被限定在方法调用的生命周期之内,或者请求的处理时间内。所以,当部署服务为请求范围的服务时,是没法管理会话的;这与没有会话是一样的。
    一旦把服务部署为请求会话范围的服务时,每个服务的实现类就会准备建立服务实例。例如以请求范围的方式建立了一个名为“foo”的服务,如果客户端调用了 10次,就会有这个服务实现类的10个实例。如果想显式的指定服务的会话范围,可以在services.xml中的service标签中增加一个 scope属性,如下:
<service name="foo" scope="request">
</service>
    即使把服务部署为请求范围内的,在Axis2中也有一些方法管理会话。一种方法是在Axis2的全局运行时(配置上下文)中保存状态,在需要的时候获取它。


    SOAP会话范围(SOAP Session Scope)
    SOAP会话的生命周期比请求会话的稍长,以SOAP会话的方式部署服务需要修改services.xml。管理SOAP会话需要客户端和服务都能识别会话,换句话说,客户端访问相同会话需要传输会话相关的数据,服务需要以会话相关的数据来验证用户。
    管理SOAP会话,客户端需要在SOAP头中增加名为“serviceGroupId”的参数。SOAP会话不仅提供一个服务调用的会话,还提供一个服务 组中多个服务的会话。只要在同一个SOAP会话中,可以在服务上下文中管理服务相关的数据;如果想在组中多个服务中共享数据,可以使用服务组上下文。
    以SOAP会话的方式部署了服务,当客户端第一次访问服务的时候,Axis2会产生“serviceGroupId”,并在wsa:ReplyTo中以引用参数的方式向客户端传输: 

[html] view plain copy
  1. <wsa:ReplyTo>   
  2.   <wsa:Address>   
  3.   http://www.w3.org/2005/08/addressing/anonymous   
  4.   </wsa:Address>   
  5.   <wsa:ReferenceParameters>   
  6.     <axis2:ServiceGroupId xmlns:axis2=   
  7.       "http://ws.apache.org/namespaces/axis2">   
  8.         urn:uuid:65E9C56F702A398A8B11513011677354   
  9.     </axis2:ServiceGroupId>   
  10.   </wsa:ReferenceParameters>   
  11. </wsa:ReplyTo>   

   如果客户端想保持在同一个会话内,它要复制那个引用参数,并在第二次调用服务的时候传回服务器。只要客户端传输了正确的 “serviceGroupId”,它就会在同一个会话内,服务也可以维护会话相关的数据。与请求会话不同,SOAP有默认的失效期,如果客户端超过30 秒没有连接服务,会话就会过期。如果客户端发送了过期的“serviceGroupId”,会得到Axis错误。
    管理SOAP会话需要衔接服务端和客户端的对应模块。部署服务为SOAP会话方式需要以如下方式修改services.xml:

[html] view plain copy
  1. <service name="foo" scope="soapsession"> </service>   


    传输会话范围(Transport Session Scope)
    在传输会话方式下,Axis2以传输相关的会话管理技术来管理会话。例如,在HTTP协议下,以HTTP cookies的方式来管理会话。会话的生命周期是由传输来控制的,而不是Axis2;Axis2在传输会话内保存服务上下文和服务组上下文,所以在会话 生命期内服务都可以访问这些上下文。
    传输会话的主要优势是在一个会话内与多个服务组交互。在SOAP会话中,无法在两个服务组间进行交互,而在传输会话中就可以。这种情况下,服务实例的数量取决于传输会话的数量。
    部署服务为传输会话方式需要以如下方式修改services.xml: 

[html] view plain copy
  1. <service name="foo" scope="transportsession"> </service>   

    如果打算使用 Axis2,以传输会话方式部署服务需要对axis2.xml做些修改。这主要是为了提高内存的使用率;否则,无论是否以传输会话方式部署服 务,Axis2都会在传输的级别建立会话对象;有了这些变化,Axis就不会创建无用的对象了。为了管理传输级别的会话,需要在axis2.xml中设置 “manageTransportSession”参数为“true”: 

[html] view plain copy
  1. <parameter name="manageTransportSession" locked="false">true</parameter>   


    应用范围(Application Scope)
    应用范围的会话与其他会话方式相比具有最长的生命周期,应用会话的生命周期与系统的生命周期一样长。如果以应用范围会话的方式部署服务,只会有服务的一个 实例,并且这个服务只有一个服务上下文。在Axis2中,如果考虑内存占用,并且不想管理会话,一个好的方法是把服务部署为应用范围的。
    当把服务部署为应用范围的会话方式时,客户端不需要传送额外的数据以使用同一个会话。部署服务为应用范围会话方式需要以如下方式修改services.xml: 

[html] view plain copy
  1. <service name="foo" scope="application"> </service>   


    服务客户端管理会话
    客户端管理会话需要做一些工作。如上所述,客户端如果想在SOAP会话和传输会话方式下保持在同一个会话中,需要传输一些会话相关的数据。对SOAP会话来说,需要复制所需的引用参数;对传输会话来说,需要访问传输以复制和发送cookies。
    为了简化开发,Axis2有内置的管理会话的功能,在客户端设置一个标志就可以了。这样,只要使用同一个客户端,就会往服务端发送会话相关的数据。所以,如果想保持在同一个会话内,最主要的一点就是使用同一个客户端调用服务。
    如果想保持在同一个会话内,可以用以下的方式创建服务客户端,并且重用客户端对象调用服务。 

[java] view plain copy
  1. Options options = new Options();   
  2. options.setManageSession(true);   
  3. ServiceClient sender = new ServiceClient();   
  4. sender.setOptions(options);   


    一旦按照如上方式创建了服务客户端,如果服务部署为SOAP会话方式,客户端会复制“serviceGroupId”并在第二次调用的时候上传服务端;如 果服务器是发送的会话id,例如HTTP cookies,客户端就会复制服务上下文(在客户端)并在第二次调用的时候发送给服务器。


    总结
    无状态是Web服务的主要特性之一,但这对高级Web服务开发来说是一个限制。使用Web服务开发企业级的应用并不容易,除非有一个会话管理层。Axis2有4中会话类型以解决企业级服务开发的问题。

在客户端需要使用setManageSession(true)打开Session管理功能。

综上所述,实现同一个WebServiceSession管理需要如下三步:

1. 使用MessageContextServiceContext获得与设置key-value对。

2. 为要进行Session管理的WebService类所对应的<service>元素添加一个scope属性,并将该属性值设为transportsession

3. 在客户端使用setManageSession(true)打开Session管理功能。

下面是一个在同一个WebService类中管理Session的例子。

先建立一个WebService类,代码如下:

[java] view plain copy
  1. package service;  
  2. import org.apache.axis2.context.ServiceContext;  
  3. import org.apache.axis2.context.MessageContext;    
  4. public class LoginService {  
  5.        public boolean login(String username, String password) {  
  6.               if("bill".equals(username) && "1234".equals(password)) {  
  7.                      //  第1步:设置key-value对  
  8.                      MessageContext mc = MessageContext.getCurrentMessageContext();  
  9.                      ServiceContext sc = mc.getServiceContext();  
  10.                      sc.setProperty("login""成功登录");      
  11.                      return true;  
  12.               } else {  
  13.                      return false;  
  14.               }  
  15.        }  
  16.        public String getLoginMsg() {  
  17.               //  第1步:获得key-value对中的value  
  18.               MessageContext mc = MessageContext.getCurrentMessageContext();  
  19.               ServiceContext sc = mc.getServiceContext();  
  20.               return (String)sc.getProperty("login");      
  21.        }  
  22. }  

LoginService类中有两个方法:logingetLoginMsg,如果login方法登录成功,会将“成功登录”字符串保存在ServiceContext对象中。如果在login方法返回true后调用getLoginMsg方法,就会返回“成功登录”。

    下面是LoginService类的配置代码(services.xml):

[html] view plain copy
  1. <!--  第2步:添加scope属性  -->  
  2. <service name="loginService" scope="transportsession">  
  3.        <description>  
  4.               登录服务  
  5.        </description>  
  6.        <parameter name="ServiceClass">  
  7.               service.LoginService  
  8.        </parameter>  
  9.        <messageReceivers>  
  10.               <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"  
  11.                      class="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />  
  12.        </messageReceivers>  
  13. </service>  

使用如下的命令生成客户端使用的stub类:

[html] view plain copy
  1. %AXIS2_HOME%\bin\wsdl2java   
  2.   
  3. -uri http://localhost:8080/axis2/services/loginService?wsdl -p client -s -o stub  
  4.   
  5. %AXIS2_HOME%\bin\wsdl2java   
  6.   
  7. -uri http://localhost:8080/axis2/services/loginService?wsdl   
  8.   
  9. -p client -s -o D:/Work/NPU/Develop/eclipse-workspace/SayHello/src  

    stub\src\client目录中生成了一个LoginServiceStub.java类,在该类中找到如下的构造句方法:

[java] view plain copy
  1. public LoginServiceStub(  
  2.   
  3. org.apache.axis2.context.ConfigurationContext configurationContext,  
  4. java.lang.String targetEndpoint, boolean useSeparateListener)  
  5. throws org.apache.axis2.AxisFault  {  
  6.            
  7.        _serviceClient.getOptions().setSoapVersionURI(  
  8.       org.apache.axiom.soap.SOAP12Constants.SOAP_ENVELOPE_NAMESPACE_URI);  
  9. }  

在该方法中最后添加如下的代码(在Axis1.5以上版本中不需要这么做)

[java] view plain copy
  1. //  第3步:打开客户端的Session管理功能  
  2. _serviceClient.getOptions().setManageSession(true);  

下面的客户端代码使用LoginServiceStub对象访问了刚才建立的WebService

[java] view plain copy
  1. LoginServiceStub stub = new LoginServiceStub();  
  2. LoginServiceStub.Login login = new LoginServiceStub.Login();  
  3. login.setUsername("bill");  
  4. login.setPassword("1234");  
  5. if(stub.login(login).local_return) {  
  6.        System.out.println(stub.getLoginMsg().local_return);  
  7. }  

运行上面的代码后,会输出“成功登录”信息。

猜你喜欢

转载自tooby.iteye.com/blog/2114325
今日推荐