IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  SOA and Web services  >

用JAX-RPC开发Web服务: EJB作为Web服务端点

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

陈亚强 (cyqcims@mail.tsinghua.edu.cn), 高级软件工程师

2003 年 7 月 01 日

本文是J2EE Web服务开发系列文章的第四篇,在上一篇里我们讨论了把Servlet作为Web服务端点来进行开发,在本篇中,我们仍然结合以前的案例,用EJB实现为Web服务端点,然后在客户端进行调用,最后把JAXM开发Web服务合JAX-RPC开发Web服务进行一个比较。关于本篇文章案例的描述请参考本系列文章的第一篇:《用JAXM开发Web服务》。

阅读本文前您需要以下的知识和工具:

  • JavaTM Web Services Developer Pack 1.1,并且会使用初步使用;
  • 至少会使用一种EJB容器来开发、部署EJB,并且了解怎么在客户端访问EJB组件;
  • 对Apache axis Web服务开发工具有基本的了解;
  • 一般的Java编程知识。

本文的参考资料见 参考资料

本文的全部代码在这里 下载

EJB作为Web服务端点技术概述

在J2EE平台中,要把EJB部署成Web服务,那么EJB必须是无状态会话Bean。在J2EE1.3以前版本里,需要结合使用其它的工具才能实现部署,比如Apache axis就提供了一个EJB Provider,通过这个EJB Provider,就可以把EJB作为Web服务来访问。在即将发布的J2EE1.4里,在很多方面增强了Web服务的支持,其中包括EJB2.1,它可以直接把无状态会话Bean部署成Web服务。关于在J2EE1.4平台把EJB直接作为Web服务开发的技术将在以后进行介绍。

IBM提供的Web Sercices Development Kit 5.0和Apache axis1.1都可以把EJB部署成Web服务。

EJB作为Web服务端点时系统的构架如图1所示。



如图所示,虽然说把EJB作为Web服务端点部署,但实际上客户端还是要通过一个中介和EJB进行交互,这个中介通常运行在Servlet容器中。

在这个案例中,将按照以下的步骤来介绍:

  • 开发EJB
  • 从EJB生成Web服务
  • 部署Web服务
  • 在客户端访问Web服务





回页首


开发EJB

待部署的EJB是BookServiceFacade,它的功能在本系列文章的第一篇(《用JAXM开发Web服务》)已经介绍过了。它的远程接口代码如下:

例程1 BookServiceFacade

package com.hellking.webservice.ejb;
import java.rmi.RemoteException;
import javax.ejb.*;
public interface BookServiceFacade extends EJBObject
{
   public java.util.Collection getAllBook    () throws RemoteException; 
    public java.util.Collection findByCategory    (String category) throws RemoteException;
    public java.util.Collection getBookDetail    (String name) throws RemoteException;
}

在BookServiceFacade里,定义了三个查找图书信息的业务方法。BookServiceFacade的部分实现代码如下:

例程2 BookServiceFacadeEJB的部分代码

public class BookServiceFacadeEJB implements SessionBean
{
…
public java.util.Collection getAllBook    ()  
    { 
    try
    {
     Collection temp= getBookEntityHome().findAllBook();
     Collection ret=convertToVO(temp);
     return ret;
     
    }
    catch(Exception ex)
      {
       ex.printStackTrace();
       return null;
      }
   }
   public java.util.Collection getBookDetail    (String name) throws RemoteException
   {
    try
    {
     BookEntity book=getBookEntityHome().findByName(name);
     BookVO re=book.getBookVo();
     java.util.Collection c=new java.util.ArrayList();
     c.add(re);
     return c;
    }
    catch(Exception ex)
    {
      ex.printStackTrace();
       return null;
      }
   }    
    /**
     * @J2EE_METHOD  --  findByCategory
     */
    public java.util.Collection findByCategory    (String category)  
    { 
    try
    {
     Collection temp= getBookEntityHome().findByCategory(category);
     Collection ret=convertToVO(temp);
     return ret;    
    }
    catch(Exception ex)
      {
       ex.printStackTrace();
       return null;
      }    
    }
…
}

从上面代码可以看出,BookServiceFacadeEJB通过调用BookEntityEJB组件的Home接口来进行查找图书信息的。BookEntityEJB是表示图书信息的实体Bean。

关于EJB的其它代码请参考 源代码





回页首


从EJB生成Web服务

编译好EJB后,对它进行打包,并且要打包成*.ear文件,对于目标运行平台是IBM Websphere,那么可以使用Websphere提供的打包工具进行打包。然后使用IBM Web Sercices Development Kit 5.0生成Web服务的部署描述和对应的客户端调用框架。具体的使用方法如下:

SET WSDK_HOME=< WSDK的安装目录>
SET PATH=%PATH%;%WSDK_HOME%/bin;.
EJB2WebService [<optional arguments>] -project <ProjectName> -ri <RemoteInterface>  <EJB.ear>

比如:

EJB2WebService -project BookService
 -ri com.hellking.webservice.ejb. BookServiceFacade book.ear

-ri参数表示待部署成Web服务的EJB远程接口,book.ear是打包好的ear文件。使用这个命令后,将使用默认值对Web服务进行绑定。当然您也可以可选的参数来指定绑定等信息。更详细的命令使用方法请参考IBM WebSphere SDK for Web Services V5.0 的帮助文件。

使用这个命令后,将生成一个更新过的BookService.ear文件,它能部署到Websphere上,另外还包括客户端调用框架:

  • BookServiceFacade_SEI.java:具体的业务方法;
  • BookServiceFacade_SEIService.java:客户端服务接口,用来获得BookServiceFacade_SEI对象的引用;
  • BookServiceFacade_SEIServiceLocator.java:服务定位器;
  • BookServiceFacadeEJBSoapBindingStub.java:客户端Stub;

如果使用Apache axis,您可以使用前一篇文章介绍的方法来生成客户端的调用框架,你只需要把BookServiceFacade接口作为生成框架的输入:

java org.apache.axis.wsdl.Java2WSDL -o temp.wsdl 
-l"http://localhost:8080/axis/services/BookServletService"
 -n "urn:BookServletService"
 -p"com.hellking.webservice" "urn:BookServletService"
 com.hellking.webservice.ejb. BookServiceFacade
java org.apache.axis.wsdl.WSDL2Java -o . -d Session -s -S true 
-Nurn:BookServletService com.hellking.webservice.ejb temp.wsdl

生成这个框架后,可能需要更改BookServiceFacade_SEIServiceLocator中的BookServiceFacadeEJB_address的值,这个值根据Web服务的部署位置来确定。





回页首


部署Web服务

如果使用Websphere作为运行环境,那么请先配置好数据库连接池,并且配置一个JNDI为jdbc/BookSource的数据源,然后在数据库建立对应的表,插入一些数据。接下来部署工作和部署其它的J2EE应用一样,在这里就不介绍了。

如果使用的是Apache axis引擎,那么编辑%WEBAPP_HOME%/WEB-INF/ server-config.wsdd文件,

增加以下内容:

<service name="BookEJBService" provider="java:EJB">
  <parameter name="allowedMethods" value="*"/>
  <parameter name="beanJndiName" value="ejb/bookfacade"/>
  <parameter name="homeInterfaceName"
 value="com.hellking.webservice.ejb.BookServiceFacadeHome"/>
  <beanMapping languageSpecificType="java:com.hellking.webservice.BookVO"
 qname="ns2:BookEJBService" xmlns:ns2="BookEJBService"/>
 </service>
 

注意这里的beanJndiName和EJB的JNDI名称是一致的,参数homeInterfaceName的值是.BookServiceFacade组件的Home接口。

启动应用服务器,在浏览器里输入一下的内容来验证Web服务是否已经成功部署:
http://localhost:8080/axis/services/BookEJBService?wsdl(Apache axis+Tomcat+Jboss)
http://localhost:6080/BookService/services/BookServiceFacadeEJB?wsdl (WSDK v5)

如果一切顺利,那么在浏览器里将显示Web服务的WSDL文件。





回页首


客户端调用

由于在传输数据时使用了BeanMapping,故需要在BookServiceFacadeEJBSoapBindingStub.java文件中注册序列化和反序列化方法。在BookServiceFacadeEJBSoapBindingStub中的getBookDetail、getAllBook和findByCategory方法中找到以下代码:

 java.lang.Object _resp = _call.invoke(new java.lang.Object[] {in0});

在这行代码前面增加以下内容:

例程3 注册BeanMapping

QName    qn      = new QName( "BookEJBService", "BookEJBService" );
 _call.registerTypeMapping(BookVO.class, qn,
                      new org.apache.axis.encoding.ser.BeanSerializerFactory(BookVO.class, qn),        
                      new org.apache.axis.encoding.ser.BeanDeserializerFactory(BookVO.class, qn));

在编写客户端程序前,使用以下代码测试调用是否成功。

例程4 客户端调用Web服务测试

package com.hellking.webservice;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
import com.hellking.webservice.ejb.*;
public class Client { 
    public static void main(String[] args) {      
        try {  
            BookServiceFacade_SEIServiceLocator locator=
new BookServiceFacade_SEIServiceLocator();
            BookServiceFacade_SEI myProxy=locator.getBookServiceFacadeEJB();
            Object[] c=myProxy.getAllBook();
            BookVO book=(BookVO)c[0];
            System.out.println(book.getName());    
        } catch (Exception ex) {
            ex.printStackTrace();
        } 
    } 
}

如果测试不成功,检查BookServiceFacade_SEIServiceLocator.java和BookServiceFacadeEJBSoapBindingStub.java的名称空间是否和部署的Web服务的WSDL文件一致。如果测试成功,那么恭喜你,你已经完成了最可能出错的任务。接下来的客户端也就是例行公事,还是和以前的方法一样,编写一个业务代表,它负责调用Web服务,并且把调用结果返回给BookClientGUI界面程序。业务代表的代码如下:

例程5 业务代表(JAXRPCDelegate.java)

package com.hellking.webservice;
/**
  *@author hellking
  */
import java.net.*;
import java.io.*;
import java.util.*;
import com.hellking.webservice.ejb.*;
import javax.xml.soap.*;
public class JAXRPCDelegate implements BookBusiness
{
 BookServiceFacade_SEIServiceLocator locator;
 BookServiceFacade_SEI bookService;
 //在构造方法中获得BookServiceFacade_SEI对象的引用
 public JAXRPCDelegate()
 {
   try
   {
    locator=new BookServiceFacade_SEIServiceLocator();
           bookService=locator.getBookServiceFacadeEJB();
         }
         catch(Exception e)
         {
  //错误处理,这里省略
         }
    }
  
 public Collection getBookByCategory(String category)
 {
  Collection ret=new ArrayList();
  try
  {   
            Object[] books=bookService.findByCategory(category);
            int i=0;
            
            while(true)
            {
             ret.add(books[i++]);
            }                 
             
  }
  catch(Exception e)
  {
   //错误处理,这里省略
  }
   return ret; 
 }
 
 public Collection getAllBooks()
 {
  Collection ret=new ArrayList();
  try
  {
   
            Object[] books=bookService.getAllBook();
            int i=0;
            
            while(true)
            {
             ret.add(books[i++]);
            }                 
             
  }
  catch(Exception e)
  {
  //错误处理,这里省略
  }
  return ret;
 }
 
 public BookVO getTheBookDetail(String name)
 {
  BookVO ret=null;
  try
  {
   
            Object[] book=bookService.getBookDetail(name);
            ret=(BookVO)book[0];               
             
  }
  catch(Exception e)
  {
   //错误处理,这里省略
  }
  return ret;  
 }
}

JAXRPCDelegate通过和BookServiceFacade_SEI对象的引用来调用Web服务的,进一步说, BookServiceFacade_SEI对象的引用就是BookServiceFacadeEJBSoapBindingStub对象的引用。

下面的任务是在BookClientGUI程序中稍做更改,即在BookClientGUI的构造方法中增加一下代码:

例程6 更改BookGUI程序

public BookClientGUI()
{
  business=new JAXRPCDelegate();
  books=business.getAllBooks();
  amount=books.size();
 }
…

运行代码:

java com.hellking.webservice.BookClientGUI

运行此客户段程序前请设置好相关的环境变量。

运行结果如图2所示。







回页首


客户端调用的其它技巧

以上使用的JAX-RPC Stub方法来调用Web服务,实际上我们也可以编写程序进行动态调用;或者把Web服务和JNDI绑定,通过JNDI lookup到这个Web服务的Service Interface,然后从Service Interface获得BookServiceFacade_SEI业务接口。

在J2EE 应用中访问Web服务

在这种方式,首先要把Web服务绑定在JNDI中,然后通过JNDI来获得Service Interface引用,并且从这个引用中获得业务接口(BookServiceFacade_SEI)。通常的调用方法如例程7所示。

例程7 在J2EE引用环境中访问Web服务

try{
…
Context ctx= new InitialContext();
        BookServiceFacade_SEIService bookInterfaceService = (BookServiceFacade_SEIService)
            ctx.lookup("java:comp/env/service/BookServiceFacade_SEIService");
 
        BookServiceFacade_SEI bookService = (BookServiceFacade_SEI)
            bookInterfaceService. getBookServiceFacadeEJB ();
        
        return bookService.getAllBooks();
}
catch(Exception e)
{
…
}
…





回页首


普通的J2SE客户端调用方法

如果普通的J2SE客户端调用Web服务,比较常用的是使用apache axis包里提供的API。例程8是普通的J2SE客户端调用方法的一个参考实现。

例程8普通的J2SE客户端调用方法

package com.hellking.webservice.ejb;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.rpc.ServiceFactory;
import com.hellking.webservice.BookVO;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;
import javax.xml.namespace.QName;
  
public class J2SEClient {
        public static void main(String [] args)
      {       
            Object[] c=getBooks();
            BookVO book=(BookVO)c[0];
            System.out.println(book.getName());       
      }
      
      public static Object[] getBooks()
      {
      try
       {
        String BookServiceFacadeEJB_address =
 "http://localhost:8080/axis/services/BookEJBService";
        java.net.URL endpoint;
        endpoint = new java.net.URL(BookServiceFacadeEJB_address);//调用的端点
              Service  service = new Service();
             Call     call    = (Call) service.createCall();//创建一个Call
         call.setReturnType(
new javax.xml.namespace.QName("http://schemas.xmlsoap.org/soap/encoding/", 
"Array"), java.lang.Object[].class);
         call.setReturnQName(new javax.xml.namespace.QName("", "getAllBookReturn"));
         call.setUseSOAPAction(true);
         call.setTargetEndpointAddress(endpoint);//设置调用端点
         call.setSOAPActionURI("");
         call.setEncodingStyle(null);
         call.setProperty(org.apache.axis.client.Call.SEND_TYPE_ATTR, Boolean.FALSE);
         call.setProperty(org.apache.axis.AxisEngine.PROP_DOMULTIREFS, Boolean.FALSE);
         call.setOperationStyle("rpc");
         call.setOperationUse("literal");
         call.setOperationName(
new javax.xml.namespace.QName("http://ejb.webservice.hellking.com", "getAllBook"));
         //注册BeanMapping
         QName    qn      = new QName( "BookEJBService", "BookEJBService" );
         call.registerTypeMapping(BookVO.class, qn,
                     new org.apache.axis.encoding.ser.BeanSerializerFactory(BookVO.class, qn),        
                  new org.apache.axis.encoding.ser.BeanDeserializerFactory(BookVO.class, qn)); 
          java.lang.Object _resp = call.invoke(new java.lang.Object[] {});
 
         if (_resp instanceof java.rmi.RemoteException) {
             throw (java.rmi.RemoteException)_resp;
         }
         else {
             try {
                 return (java.lang.Object[]) _resp;
             } catch (java.lang.Exception _exception) {
                 return (java.lang.Object[])
 org.apache.axis.utils.JavaUtils.convert(_resp, java.lang.Object[].class);
             }
          }
        }
        catch(Exception e)
        {
           e.printStackTrace();
           return null;           
    }
    }
}

从代码中可以看到,要调用Web服务,首先创建一个Service对象,然后使用这个Service对象创建一个Call,接下来设置Call的一些属性:服务端点的URL、操作名、返回的数据类型、注册BeanMapping(可选)等。call.invoke(new java.lang.Object[] {})命令将调用Web服务。在这里,没有为Call提供输入参数,如果调用需要参数,那么可以这样调用:

Object param=new Object[]{"param1","param2"};
Call.invoke(param);





回页首


总结

JAXM和JAX-RPC开发Web服务的比较。

比较项目JAXMJAX-RPC
服务端JAXM ServletServlet、Message、EJB
消息的类型One Way(异步)、Request-Response(同步)支持异步Message(如JMS)、也可以同步调用
服务端编程服务端Servlet扩展JAXMServlet 只要实现onMessage方法即可定义服务端点,服务端点扩展java.rmi.Remote接口;
服务端点实现类实现了服务端点接口。
可以使用一些工具生成服务端框架(Skeleton,实现类,服务接口)
客户端编程创建 SOAP 连接
创建 SOAP 消息
在SOAP消息里增加数据发送消息,获得结果(Request-Response)
对SOAP应答进行处理
创建Service对象
创建Call
设置Call的一些属性
调用Web 服务
对结果进行处理
(以上的调用可以使用JAX-RPC工具自动生成代码)
编程难度对于处理SOAP消息比较复杂,但是编程模型比较简单可以不直接处理SOAP消息,这个过程是通过Stub和Skeleton完成。编程模型比较复杂,但是要编写的代码比较少。

通过上面的比较,可以看出,在一般情况下,推荐使用JAX-RPC实现Web服务。





回页首


下一步

在对JAX-RPC编程有了基本的了解后,下一篇文章将讨论在SOAP消息中传输BLOB和CLOB数据的问题。



参考资料



关于作者

陈亚强:北京华园天一科技有限公司高级软件工程师,擅长J2EE技术,曾参与多个J2EE项目的设计和开发,对Web服务有很大的兴趣并且有一定的项目经验。热爱学习,喜欢新技术。即将由电子工业出版社出版的《J2EE企业应用开发》正在最终定稿阶段,目前正从事J2EE方面的开发和J2EE Web服务方面的图书写作。您可以通过 cyqcims@mail.tsinghua.edu.cn和他联系。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?







回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款