级别: 初级 陈亚强 (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服务的比较。
| 比较项目 | JAXM | JAX-RPC | | 服务端 | JAXM Servlet | Servlet、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和他联系。
|
对本文的评价
|