UML软件工程组织

通过 WebSphere Studio 和 WebSphere Application Server 将 Web 服务实现为有状态会话 bean

选自:Tatiana Lavrentieva (tlavrent@ca.ibm.com)
DB2 Records Manager team,Ottowa,Canada,IBM

 

本文描述了如何扩展 WebSphere Application Server V5.02 Web 服务运行时来支持实现为有状态会话 bean 的 Web 服务。其中包含了样本代码和详细说明,并用 WebSphere Studio Application Developer V5.1 来论证和测试它的新功能。

引言
从 WebSphere® Application Server V5.02 起,IBM® 就引入了一种新的 Web 服务运行时,它支持 JSR 101(JAX-RPC)程序设计模型和 JSR 109(J2EE Web 服务)部署模型。JSR 109 的重点是解决如何将 Web 服务实现为 servlet 和无状态会话 bean 以及它们的生命周期的问题。然而,这一规范以及 Application Server V5.02 运行时并没有解决将有状态会话 bean 作为 Web 服务公开的问题。

有状态会话 bean 是对会话业务流程进行建模的一种理想模型。如果将所有业务流程建模为无状态形式,接下来再从数据库中重建状态,这样会导致性能的显著降低[1]。这就是为什么有状态会话 bean 成为 J2EE 开发者工具箱中的必备工具的原因。将有状态会话 bean 作为 Web 服务公开对任何的 J2EE Web 服务实现都是有好处的。

下面提供了一个名为 TestStatefulWebServices.zip 的下载文件。请将它解压并部署到 WebSphere Application Server V5.02 中,而无需任何其他步骤。要将 TestStatefulWebServices 应用程序部署到 WebSphere Application Server V5.1 中,您必须重新生成 EJB 的部署代码。

高级设计
通过使用来自 Open Grid Services Architecture (OGSA)的 WS-Resource Framework 方法,您可以很容易地找出目前 WebSphere Application Server V5.02 Web Services 运行时的局限性[2]。OGSA 建议将应用程序作为可能有状态的服务的集合公开,这些服务遵守一组满足创建、生命周期管理等目的的约定。这些服务是在运行时通过工厂(Factory)服务创建的,工厂(Factory)服务返回一个特定于服务的句柄或一个 ID 来惟一而永久性地标识该实例——在我们的例子中它是一个基于 64 位编码的、序列化的 EJBObject Handle。通过调用有状态服务,用户可以将句柄当作信息头条目来使用,这样就能够保证服务的同一实例能够用在所有的调用中。Factory 服务也负责删除有状态服务实例。从性能方面考虑,有状态会话 bean 在使用过后就删除是很重要的。

以下交互图显示了序列中最重要的方法调用:

图 1. 交互图
图 1. 交互图

实现
WebSphere Application Server V5.02 运行时是基于 Apache Axis V1.0 实现的[3]。Axis 框架很容易扩展,它可以让您在引擎中插入扩展程序(处理程序、提供者等等)来自定义处理。在 Axis 框架中,提供者是一个实体类,它调用 Web 服务操作。我们将会创建一个自定义的 EJB 提供者来支持我们的方法。WebSphere Application Server V5.02 Web Services 运行时使用 com.ibm.ws.webservices.axis.ws.providers.java.WASEjbProvider 来访问无状态会话 bean。我们的 custom StatefulEJBProvider 会简单地继承 WASEjbProvider 类,这样就可使用现有的功能。添加以下的代码行就可以从 SOAP 消息中检索 Service ID 并返回相应的有状态会话 bean。

清单 1. 有状态 EJB 提供者

package com.ibm.ws.webservices.axis.ws.providers.java;

import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.util.Iterator;

import javax.ejb.EJBObject;
import javax.ejb.Handle;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPHeaderElement;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;

import com.ibm.ws.webservices.engine.MessageContext;
import com.ibm.ws.webservices.engine.encoding.Base64;

public class StatefulEjbProvider extends WASEjbProvider
{
    protected Object makeNewServiceObject(MessageContext messagecontext, String s)
        throws Exception
    {
		String id = getId(messagecontext);
		if(id != null){
			/* return stateful service */
			return this.deSerialize(id);
		}
		else
/* return stateless service */
			return super.makeNewServiceObject(messagecontext,s);
    }
    /* Your Factory service should have the matching serialize method available*/
	private EJBObject deSerialize(String id) {
	  EJBObject ejbObject= null;
	  try {
		byte[] idBytes = Base64.decode(id);
		ByteArrayInputStream byteStream = new ByteArrayInputStream(idBytes);
		ObjectInputStream objStream = new ObjectInputStream(byteStream);
		Handle ejbHandle = (Handle)objStream.readObject();
		objStream.close();
		ejbObject = ejbHandle.getEJBObject();
	  } catch (Exception e) {e.printStackTrace();}
	  return ejbObject;
	}

	private String getId(MessageContext messagecontext) throws Exception{
		String id = null;
		SOAPMessage message = messagecontext.getMessage();
		SOAPPart sp = message.getSOAPPart();
		SOAPEnvelope se = sp.getEnvelope();
		SOAPHeader header =  se.getHeader();
		Iterator it = header.getChildElements();
		while (it.hasNext()){
			SOAPHeaderElement he = (SOAPHeaderElement) it.next();
			if (he.getElementName().getLocalName().equals("id"))
				id =  he.getValue();
		}
		return id;
	}
}

要启用 StatefulEJBProvider,您必须执行以下步骤:

  1. StatefulEJBProvider.class 文件添加到 webservices.jar,它位于 WebSphere Application Server V5.02 及其后续版本的 lib 目录下。
  2. 在同一 webservices.jar 中的 META-INF/was-webservices.xml 文件里修改缺省的运行时 EJB 提供者,将 WASEjbProvider 更改为 StatefulEjbProvider,如下所示:
    
    <provider>
     	 <name>WASEJB</name>
      <javaclass>com.ibm.ws.webservices.axis.ws.providers.java.StatefulEjbProvider</javaclass>
    </provider>
    

在 WebSphere Studio Application Developer V 5.1 中测试有状态会话 bean Web 服务
这种方法已经在 WebSphere Application Server V5.02 和 V5.1 以及 WebSphere Studio Application Developer V5.1 和 V5.1.1 中成功通过测试。

服务器端视图
您应该创建有状态会话 bean,将其作为有状态服务的后端,并为 Factory 服务创建一个无状态 Factory bean(工厂 bean)。或者,您还可以下载下面的样本代码。这里是该样本代码的类图:

图 2. 样本应用程序类图

如果要让 WebSphere Studio Application Developer V5.1 生成 WSDL 文件以及所有必需的构件,您应该在 ejb-jar.xml 中将有状态会话 bean 标记为无状态。请运行 Web Services 向导来生成端点接口(SEI)、实体类和 WSDL 文件。选择 WebSphere Application Server V5.02 作为您的 Web 服务的服务器端运行时[4],同时切记将您的 bean 改回有状态的。

客户端视图
JSR-109 定义了三个不同的机制,J2EE Web 服务客户端能够使用它们来调用 Web 服务:

  • 静态存根
  • 动态代理
  • 动态调用接口(DII)

本文的样本代码用的是静态存根方法。静态存根是一个 Java 类,它是作为代理提供给 Web 服务的。Run Web 服务客户端向导通过任何的客户端 Web 服务运行时选项来从您新建的 WSDL 文件中生成存根类[4]。我将 Axis V1.0 作为客户端运行时。惟一需要做的其他工作是为有状态服务的每次调用设置一个服务 ID。这里使用 StatefulEjbProvider 来测试样本有状态会话 bean 的代码:

清单 2. 测试客户端

public class TestClient {

	public static void main(String[] args) {
		try {
  		/* Locate factory service */
		FactoryServiceLocator factoryLocator = new FactoryServiceLocator();
		Factory factoryService= factoryLocator.getFactory();
		/* Create instance of stateful service and get a serviceId for it */
		String statefulService1Handle = factoryService.createStatefulService1("param");
		String statefulService2Handle = factoryService.createStatefulService2("param",2);

  		/* Locate stateful service */
		StatefulService1ServiceLocator statefulService1ServiceLocator =
		new StatefulService1ServiceLocator();

		StatefulService1SoapBindingStub statefulService1=
		(StatefulService1SoapBindingStub) 
			statefulService1ServiceLocator.getStatefulService1();

        /* Set serviceId to locate the specific instance of stateful service*/
		statefulService1.setServiceId(statefulService1Handle);

		/* Call a method on the stateful service */
		System.out.println("StatefulService1. state : " + statefulService1.getStateS1());

		/* Destroy the instance of stateful service*/
		factoryService.removeStatefulService(statefulService1Handle);

		StatefulService2ServiceLocator statefulService2ServiceLocator =
 		new StatefulService2ServiceLocator();

		StatefulService2SoapBindingStub statefulService2=
		(StatefulService2SoapBindingStub) 
			statefulService2ServiceLocator.getStatefulService2();

		/* Set serviceId to locate the specific instance of stateful service*/
		statefulService2.setServiceId(statefulService2Handle);

		System.out.println("StatefulService2. state : " + statefulService2.getStateS2());

		factoryService.removeStatefulService(statefulService2Handle);

	} catch (Exception e) {
		e.printStackTrace();
	}
}

以下是客户端存根类的代码片断:

清单 3. 客户端存根

public class StatefulService1SoapBindingStub extends org.apache.axis.client.Stub
		implements com.ibm.StatefulService1 {
    private String serviceId;
    private org.apache.axis.client.Call createCall() throws java.rmi.RemoteException {
        try {
            org.apache.axis.client.Call _call =
                    (org.apache.axis.client.Call) super.service.createCall();
			     ...
			/* set serviceId header entry */
	if (this.serviceId != null){
		SOAPHeaderElement element = new SOAPHeaderElement("http://ibm.com", "id");
		try {
			element.setObjectValue(this.serviceId);
		} catch (SOAPException e) {...}
	}
	_call.addHeader(element);
	}
            return _call;
        }
        catch (java.lang.Throwable t) {...}
    }

    public void setStateS1(java.lang.String newState) throws java.rmi.RemoteException {...}

    public java.lang.String getStateS1() throws java.rmi.RemoteException {...}

     public void setServiceId(String string) {
serviceId = string;
     }
}

结束语
本文为您描述了如何扩展 WebSphere Application Server V5.02 Web 服务运行时来支持后端为有状态会话 bean 的 Web 服务。应用这里描述的方法将有助于您解决跨平台和语言的互操作性的复杂问题。

参考资料

 

关于作者
Tatiana Lavrentieva 是一名具有八年企业应用程序开发经验的 IT 专职人员。Tatiana 持有一些技术证书,其中包括:Sun 认证的 J2EE 企业架构师(Sun Certified Enterprise Architect for J2EE)、Sun 认证的业务组件开发人员(Sun Certified Business Component Developer)、IBM 认证的 WebSphere Application Server 和 WebSphere Studio 企业开发人员(IBM Certified Enterprise Developer for WebSphere Application Server and WebSphere Studio)。Tatiana 目前在 IBM DB2 Records Manager 小组工作。您可以通过 tlavrent@ca.ibm.com 与 Tatiana 联系。
 

版权所有:UML软件工程组织