AXIS实现Web服务深入篇

AXIS实现Web服务深入篇
http://www-128.ibm.com/developerworks/cn/webservices/.backup/ws-deepaxis/

一直使用J2EE从事移动业务方面的开发,
2004 年 2 月
本文主要介绍使用service方式实现Web服务、复杂类型参数或者返回值以及面向消息/文档的服务类型,同时还会简单提及Web服务的会话管理以及安全问题等等。

前段时间我的一篇文章《应用AXIS开始Web服务之旅》介绍了如何通过AXIS这个项目来实现Web服务的功能。该文章主要介绍AXIS的结构、如何使用jws文件的方式开发一个简单的Web服务,并用了比较大的篇幅来介绍Web服务的客户端编程,应该说是使用AXIS开发Web服务的入门篇,本文假设你已经看过《应用AXIS开始Web服务之旅》并对AXIS有一定的基础,在这个基础上我们将要介绍的内容有几个方面包括使用service方式实现Web服务、复杂类型参数或者返回值以及面向消息/文档的服务类型,同时还会简单提及Web服务的会话管理以及安全问题等等。

在开始我们的文章之前,我们还需要搭建一个环境,我们需要一个支持Web服务的web应用程序并假设名字为axis,如何建立请参照《应用AXIS开始Web服务之旅》文章中的介绍。

使用定制发布编写Web服务

使用jws文件的方式编写Web服务具有方便、快捷的优点,它可以很快的将你已有的类发布成Web服务。但是更多的时候这并不是一个好的主意,因为这种做法引发的问题是我们必须要将已有类的源码发布出来,因为更多的时候我们并不想这样做;另外虽然你可以先用工具开发并调试完毕一个java文件后再改名为jws,但是这多少有些便扭,而且并不是类中的所有方法你都想发布成可通过Web服务来访问的,这时候你就必须将这些方法的修饰符改为不是public的,这就跟你原有的类不同步,以后的修改将会更加的麻烦。

在这里我把定制发布方式称为service方式,就好像JSP的出现不会使Servlet失宠的道理一样,有了jws,service方式还是有它的用武之地,而且是大放异彩。发布一个service方式的Web服务需要两部分内容:类文件以及Web服务发布描述文件。下面我们使用一个简单的例子来讲述这个过程。

首先我们需要一个service类,这个类跟普通的类没有任何区别,下面是我们实现一个城市便民服务的类,我们需要将CityService类的两个方法getZip和getTel发布成Web服务,编译该文件并把class文件拷贝到<webapp>/WEB-INF/classes对应目录下。



Package lius.axis.demo;
/** 
  * 该类实现了城市服务,用于发布成Web服务 
  * @author Liudong 
  */
  
  public class CityService {
  /**     
    * 获取指定城市的邮编     
    * @param city     
    * @return     
    */
    public String getZip(String city) { 
 return "510630";
    }
    
    /**     
      * 获取指定城市的长途区号     
      * @param city     
      * @return     
      */ 
    
    public String getTel(String city) {
 return "020"; 
    }
}

程序已经完成,下面是发布这个Web服务。打开<webapp>/WEB-INF/server-config.wsdd如果这个文件不存在则创建一个新的文件,内容如下:



<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns="http://xml.apache.org/axis/wsdd/" 
	xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"> 
 <globalConfiguration>  
  <parameter name="adminPassword" value="admin"/>  
  <parameter name="attachments.implementation" 
  	value="org.apache.axis.attachments.AttachmentsImpl"/>  
  <parameter name="sendXsiTypes" value="true"/>  
  <parameter name="sendMultiRefs" value="true"/>  
  <parameter name="sendXMLDeclaration" value="true"/>  
  <parameter name="axis.sendMinimizedElements" value="true"/>  
  <requestFlow>   
   <handler type="java:org.apache.axis.handlers.JWSHandler">    
    <parameter name="scope" value="session"/>   
   </handler>   
   <handler type="java:org.apache.axis.handlers.JWSHandler">    
    <parameter name="scope" value="request"/>    
    <parameter name="extension" value=".jwr"/>   
   </handler>  
  </requestFlow> 
 </globalConfiguration> 
 <handler name="LocalResponder" type="java:org.apache.axis.transport.local.LocalResponder"/> 
 <handler name="URLMapper" type="java:org.apache.axis.handlers.http.URLMapper"/> 
 <handler name="Authenticate" type="java:org.apache.axis.handlers.SimpleAuthenticationHandler"/>   

 <service name="city" provider="java:RPC">  
  <!-- 服务类名 -->   
  <parameter name="className" value="lius.axis.demo.CityService"/>  
  <!-- 允许访问所有方法 -->  
  <parameter name="allowedMethods" value="*"/> 
 </service>  
 <transport name="http">  
  <requestFlow>   
   <handler type="URLMapper"/>   
   <handler type="java:org.apache.axis.handlers.http.HTTPAuthHandler"/>  
  </requestFlow> 
 </transport> 
 <transport name="local">  
  <responseFlow>   
   <handler type="LocalResponder"/>  
  </responseFlow> 
 </transport>
</deployment>

其中粗斜体的部分是我们服务的配置信息,启动Tomcat并打开浏览求访问地址: http://localhost:8080/axis/services/city?wsdl ,下面是浏览器显示我们Web服务的WDSL数据。


当然了,这个过程比起jws方式来说是稍微麻烦一点,你可以把它想象成你发布一个servlet一样,创建servlet类然后在web.xml中加入配置信息。

处理复杂类型参数和返回值

之前我们做的演示程序都很简单,方法的参数和返回值都是简单类型的数据,但是在实际应用过程中往往没有这么简单。在使用面向对象的编程语言时,我们会希望数据类型可以是某个对象,比如我们提供一个接口用来发送短信息,那么我们希望接口的参数是一个消息对象,这个消息对象封装了一条信息的所有内容包括发送者、接收者、发送时间、优先级、信息内容等等,如果我们把每个内容都做成一个参数,那这个接口的参数可能会非常的多。因此封装成对象是很有必要的。

在使用Axis来编写Web服务时对复杂类型数据的处理同样也是非常简单。Axis要求复杂类型对象的编写必须符合JavaBean的规范,简单的说就是对象的属性是通过getter/setter方法来访问的。来看看下面这个简单的例子所输出的WSDL信息有何特殊的地方。为了简便,我们还是使用jws来编写,需要编写三个文件:sms.jws,Message.java,Response.java。



//文件名:sms.jws
import lius.axis.demo.*;

public class sms{  

/**  
  * 短信息发送Web服务接口  
  */
  
  public Response send(Message msg) throws Exception{
 System.out.println("CONTENT:"+msg.getContent());
 Response res = new Response();
 res.setMessage(msg);
 res.setCode(0);
 res.setErrorText("");
 return res;    
  } 
}


//Message.javapackage lius.axis.demo;

/** 
  * 欲发送的信息 
  * @author Liudong 
  */
  
public class Message {
 private String from;
 private String to;
 private String content;
 private int priority;
 
 public String getContent() {
  return content;
 }
 
 public void setContent(String content) {
  this.content = content;
 } 
 
 public String getFrom() { 
  return from;
 }
 
 public void setFrom(String from) { 
  this.from = from;
 }    
 
 public int getPriority() {
  return priority;
 }
 
 public void setPriority(int priority) {
  this.priority = priority;
 } 
 
 public String getTo() {
  return to;
 } 
 
 public void setTo(String to) {
  this.to = to;
 }
}


//Response.javapackage lius.axis.demo;

/** 
  * 信息发送回应,在这里我们做了一个对Message 类的引用 
  * @author Liudong 
  */
  
public class Response {
 private int code; 
 
 //发送结果代码 
 private String errorText;
 private Message message;
 
 //发送的原始信息  
 public int getCode() {
  return code;
 } 
 
 public void setCode(int code) {
  this.code = code;
 } 
 
 public String getErrorText() { 
  return errorText;
 } 
 
 public void setErrorText(String errorText) {   
  this.errorText = errorText;
 } 
 
 public Message getMessage() {
  return message;
 } 

 
 public void setMessage(Message message) { 
  this.message = message;
 }
}

编译Message.java和Response.java并将编译后的类文件拷贝到axis/WEB-INF/classes对应包的目录下,sms.jws拷贝到axis目录,访问http://localhost:8080/axis/sms.jws?wsdl即可看到WSDL信息,这些信息与之前不同的在于下面列出的内容(注意粗斜体部分内容):



<wsdl:types>
<schema targetNamespace="http://demo.axis.lius" xmlns="http://www.w3.org/2001/XMLSchema">
  <import namespace="http://schemas.xmlsoap.org/soap/encoding/" />
  <complexType name="Message">
    <sequence>
      <element name="content" nillable="true" type="xsd:string" />
      <element name="from" nillable="true" type="xsd:string" />
      <element name="priority" type="xsd:int" />
      <element name="to" nillable="true" type="xsd:string" />
    </sequence>
  </complexType>
  <complexType name="Response">
    <sequence>
      <element name="code" type="xsd:int" />
      <element name="errorText" nillable="true" type="xsd:string" />
      <element name="message" nillable="true" type="tns1:Message" />
    </sequence>
  </complexType>
</schema>
</wsdl:types>

这里定义了两个类型Message和Response,就是我们接口的参数类型以及返回值的类型。现在再使用WSDL2Java工具来生成客户端Helper类看看Axis帮我们做了什么?它会自动帮我们生成两个类Message和Response,包名与类名都跟我们刚才定义的一致,你可以打开看看跟我们刚才编写的类差别多大?这两个类添加了很多方法例如getTypeDesc、getSerializer、getDeserializer等等。现在你就可以随便写个小程序测试一下了,我们就不在累赘了。Service方式Web服务的处理跟jws是类似的,不同在于service方式需要在server-config.wsdd添加类型的映射配置,下面给出一个配置的示例,读者可以根据实际情况进行修改。




<service name="SmsService" provider="java:RPC">
 <parameter name="className" value="lius.axis.demo.SmsService"/>
 <parameter name="allowedMethods" value="send"/>
 <operation name="send" returnType="ns:Response">
  <parameter name="msg" type="ns:Message"/>
 </operation>
 
<!-- 这里定义了方法的参数以及返回值 -->

 <typeMapping deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"   
 encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
 qname="ns:Message"
 serializer="org.apache.axis.encoding.ser.BeanSerializerFactory"
 type="java:lius.axis.demo.Message" xmlns:ns="SmsService"/>
 
 <typeMapping
 deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"    
 	encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
 qname="ns:Response"    
 serializer="org.apache.axis.encoding.ser.BeanSerializerFactory"    
 type="java:lius.axis.demo.Response" xmlns:ns="SmsService"/>
 </service>

其他编程语言也都可以借助语言本身所附带的工具来生成这些复杂类型,如果你嫌麻烦的话可以使用XML来描述这些复杂类型,这样就简单很多。

面向消息/文档的Web服务类型

我们前面介绍的服务方式是基于RPC(远程过程调用)方式,这也是Web服务最常用的方式。面向消息/文档的的类型跟RPC不同的是它提供了一个更底层的抽象,要求更多的编程工作。客户端可以传入任何的XML文档,得到的响应不一定是SOAPEnvelope,可以返回任何它所需要的东西,甚至不返回。虽然这对开发者来说非常的灵活,但是这种通讯类型在实际的应用中并不常见。面向消息/文档的Web服务主要适合于下面几种情况,比如批量处理,基于表单的数据导入,有需要返回非XML数据时,Web服务器实现中要求直接访问传输层等等。

对于RPC类型的服务需要在全局配置文件server-config.wsdd中设置一行<service ... provider="java:RPC">,其中RPC就是服务的方式,而对于面向消息/文档的服务类型那java:RPC就要替换成为Message,并且面向消息/文档的服务类型必须通过WSDD配置来发表。对于完成面向消息服务的类,其方法必须具有以下的格式:



public Element[] methodName(Element [] elems)

其中methodName为你自定义的方法名。在Axis的目录下可以找到MessageService.java,这就是一个完成了该类型的服务类,该服务的在WSDD中的配置如下:



<deployment name="test" xmlns="http://xml.apache.org/axis/wsdd/"      
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"
xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance">
 <service name="MessageService" style="message">
  <parameter name="className" value="samples.message.MessageService"/>
  <parameter name="allowedMethods" value="methodName"/> 
 </service>
</deployment>

不管是什么内容的Web服务,对客户端来说都是一样的,使用WSDL2Java来生成的客户端Helper类的MessageService接口如下:



/** 
  * MessageService.java 
  * 
  * This file was auto-generated from WSDL 
  * by the Apache Axis WSDL2Java emitter. 
  */
  
package liudong.axis.services.MessageService;

public interface MessageService extends java.rmi.Remote {
 public java.lang.Object echoElements(java.lang.Object part) throws java.rmi.RemoteException;
}

我从哪里可以获得…

Axis文档中有一句话很有意思,对于绝大多数类似于"我从哪里可以获得…"的问题,答案都在MessageContext类中。通过MessageContext类你可以获取下面几个内容,一个AxisEngine实例的引用;请求以及回应的信息;验证信息以及对于Servlet规范中的实例引用等等。例如当我们需要客户端的IP地址时我们可以通过下面代码片段获取:



/**      
  * 获取客户端请求     
  * @return     
  */
  
private HttpServletRequest getRequest() throws Exception{
 MessageContext context = MessageContext.getCurrentContext();
 HttpServletRequest req = (HttpServletRequest)context.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST);        
 return req.getRemoteHost();
}

在类HTTPConstants中,所有以MC_开头的常量就是你所可以获取到的信息,例如上面通过MC_HTTP_SERVLETREQUEST获取对应Servlet规范中的HTTP请求。更详细的信息可以通过查询API文档获取。

Web服务会话管理

在Web服务中我们可以借助HTTP以及HTTP Cookie来处理会话信息。前面我们介绍了大多数对Axis的管理都是通过MessageContext实例来完成的。下面的例子首先验证用户的登录帐号与口令如果正确则在会话中保存用户的登录信息,并提供接口供客户端获取密码。



import org.apache.axis.MessageContext;
import org.apache.axis.session.Session;

public class login{ 
 public boolean login(String user, String pass){ 
  MessageContext mc = MessageContext.getCurrentContext();
  Session session = mc.getSession();
  session.set("user",user);
  
  //保存用户名与口令
  session.set("pass",pass);
  return true;
 }
 
 public String getPassword(String user){
  MessageContext mc = MessageContext.getCurrentContext();
  Session session = mc.getSession();
  if(user.equals(session.get("user"))) 
   return (String)session.get("pass");
  return null;
 }
}

对于服务器端来讲只需要通过MessageContext实例获取Session对象即可进行会话级的数据保存或者读取,而对于通过Axis的WSDL2Java工具生成的客户端来讲还需要做一个特殊的设置,请看下面客户端代码片段。



public static void main(String[] args) throws Exception {
 LoginServiceLocator lsl = new LoginServiceLocator();
        lsl.setMaintainSession(true);
        Login login = lsl.getlogin();
        if(login.login("ld","haha")) 
  System.out.println("PWD:"+login.getPassword("ld"));
        else  
  System.out.println("Login failed.");
}

代码中的粗体部分就是让Axis帮助我们自动处理服务器返回的Cookie信息以保证会话正常工作。

保护Web服务

网络的安全问题永远是需要最先考虑的问题,可是怎么能让我们的Web服务更加安全呢?为此Axis建议可以根据实际的需要采取以下的几种方法。

  1. 使用HTTPS传输方式 该方式需要在Web服务器上进行配置同时需要客户端的支持。该措施有效的防止数据在网络传输过程中被窥视。
  2. 重命名Axis已有的一些名字,例如AdminService、AxisServlet,删除Axis目录下一些无用的程序,例如happyaxis.jsp以及一些无用的jar包等。
  3. 通过设置axis.enableListQuery的值为false来停止AxisServlet列出所有服务的功能。
  4. 禁止自动生成WSDL的功能
  5. 使用过滤器来增加一些验证功能,例如客户端的地址等。

最常用的不外乎上面几个,至于更详细的资料可以参考Axis解压目录下的docs/reference.html文件的详细介绍。

参考资料

  • 《应用AXIS开始Web服务之旅》 http://www.ibm.com/developerworks/cn/webservices/ws-startaxis/index.html

  • IBM开发者站点Web服务专区 http://www.ibm.com/developerworks/cn/webservices

  • Apache网站AXIS项目 http://ws.apache.org/axis/

  • W3C之Web服务 http://www.w3.org/2002/ws/

关于作者
刘冬,一直使用J2EE从事移动业务方面的开发。现在可以通过Java自由人网站来跟我联系,网址是: http://www.javayou.com;另外我的邮件地址是 [email protected]

你可能感兴趣的:(AXIS实现Web服务深入篇)