http://www-128.ibm.com/developerworks/cn/webservices/.backup/ws-deepaxis/一直使用J2EE從事移動業(yè)務方面的開發(fā),
2004 年 2 月
本文主要介紹使用service方式實現Web服務、復雜類型參數或者返回值以及面向消息/文檔的服務類型,同時還會簡單提及Web服務的會話管理以及安全問題等等。
前段時間我的一篇文章《應用AXIS開始Web服務之旅》介紹了如何通過AXIS這個項目來實現Web服務的功能。該文章主要介紹AXIS的結構、如何使用jws文件的方式開發(fā)一個簡單的Web服務,并用了比較大的篇幅來介紹Web服務的客戶端編程,應該說是使用AXIS開發(fā)Web服務的入門篇,本文假設你已經看過《應用AXIS開始Web服務之旅》并對AXIS有一定的基礎,在這個基礎上我們將要介紹的內容有幾個方面包括使用service方式實現Web服務、復雜類型參數或者返回值以及面向消息/文檔的服務類型,同時還會簡單提及Web服務的會話管理以及安全問題等等。
在開始我們的文章之前,我們還需要搭建一個環(huán)境,我們需要一個支持Web服務的web應用程序并假設名字為axis,如何建立請參照《應用AXIS開始Web服務之旅》文章中的介紹。
使用定制發(fā)布編寫Web服務
使用jws文件的方式編寫Web服務具有方便、快捷的優(yōu)點,它可以很快的將你已有的類發(fā)布成Web服務。但是更多的時候這并不是一個好的主意,因為這種做法引發(fā)的問題是我們必須要將已有類的源碼發(fā)布出來,因為更多的時候我們并不想這樣做;另外雖然你可以先用工具開發(fā)并調試完畢一個java文件后再改名為jws,但是這多少有些便扭,而且并不是類中的所有方法你都想發(fā)布成可通過Web服務來訪問的,這時候你就必須將這些方法的修飾符改為不是public的,這就跟你原有的類不同步,以后的修改將會更加的麻煩。
在這里我把定制發(fā)布方式稱為service方式,就好像JSP的出現不會使Servlet失寵的道理一樣,有了jws,service方式還是有它的用武之地,而且是大放異彩。發(fā)布一個service方式的Web服務需要兩部分內容:類文件以及Web服務發(fā)布描述文件。下面我們使用一個簡單的例子來講述這個過程。
首先我們需要一個service類,這個類跟普通的類沒有任何區(qū)別,下面是我們實現一個城市便民服務的類,我們需要將CityService類的兩個方法getZip和getTel發(fā)布成Web服務,編譯該文件并把class文件拷貝到<webapp>/WEB-INF/classes對應目錄下。
Package lius.axis.demo;
/**
* 該類實現了城市服務,用于發(fā)布成Web服務
* @author Liudong
*/
public class CityService {
/**
* 獲取指定城市的郵編
* @param city
* @return
*/
public String getZip(String city) {
return "510630";
}
/**
* 獲取指定城市的長途區(qū)號
* @param city
* @return
*/
public String getTel(String city) {
return "020";
}
}
|
程序已經完成,下面是發(fā)布這個Web服務。打開<webapp>/WEB-INF/server-config.wsdd如果這個文件不存在則創(chuàng)建一個新的文件,內容如下:
<?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方式來說是稍微麻煩一點,你可以把它想象成你發(fā)布一個servlet一樣,創(chuàng)建servlet類然后在web.xml中加入配置信息。
處理復雜類型參數和返回值
之前我們做的演示程序都很簡單,方法的參數和返回值都是簡單類型的數據,但是在實際應用過程中往往沒有這么簡單。在使用面向對象的編程語言時,我們會希望數據類型可以是某個對象,比如我們提供一個接口用來發(fā)送短信息,那么我們希望接口的參數是一個消息對象,這個消息對象封裝了一條信息的所有內容包括發(fā)送者、接收者、發(fā)送時間、優(yōu)先級、信息內容等等,如果我們把每個內容都做成一個參數,那這個接口的參數可能會非常的多。因此封裝成對象是很有必要的。
在使用Axis來編寫Web服務時對復雜類型數據的處理同樣也是非常簡單。Axis要求復雜類型對象的編寫必須符合JavaBean的規(guī)范,簡單的說就是對象的屬性是通過getter/setter方法來訪問的。來看看下面這個簡單的例子所輸出的WSDL信息有何特殊的地方。為了簡便,我們還是使用jws來編寫,需要編寫三個文件:sms.jws,Message.java,Response.java。
//文件名:sms.jws
import lius.axis.demo.*;
public class sms{
/**
* 短信息發(fā)送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;
/**
* 欲發(fā)送的信息
* @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;
/**
* 信息發(fā)送回應,在這里我們做了一個對Message 類的引用
* @author Liudong
*/
public class Response {
private int code;
//發(fā)送結果代碼
private String errorText;
private Message message;
//發(fā)送的原始信息
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,可以返回任何它所需要的東西,甚至不返回。雖然這對開發(fā)者來說非常的靈活,但是這種通訊類型在實際的應用中并不常見。面向消息/文檔的Web服務主要適合于下面幾種情況,比如批量處理,基于表單的數據導入,有需要返回非XML數據時,Web服務器實現中要求直接訪問傳輸層等等。
對于RPC類型的服務需要在全局配置文件server-config.wsdd中設置一行<service ... provider="java:RPC">,其中RPC就是服務的方式,而對于面向消息/文檔的服務類型那java:RPC就要替換成為Message,并且面向消息/文檔的服務類型必須通過WSDD配置來發(fā)表。對于完成面向消息服務的類,其方法必須具有以下的格式:
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規(guī)范中的實例引用等等。例如當我們需要客戶端的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規(guī)范中的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建議可以根據實際的需要采取以下的幾種方法。
- 使用HTTPS傳輸方式 該方式需要在Web服務器上進行配置同時需要客戶端的支持。該措施有效的防止數據在網絡傳輸過程中被窺視。
- 重命名Axis已有的一些名字,例如AdminService、AxisServlet,刪除Axis目錄下一些無用的程序,例如happyaxis.jsp以及一些無用的jar包等。
- 通過設置axis.enableListQuery的值為false來停止AxisServlet列出所有服務的功能。
- 禁止自動生成WSDL的功能
- 使用過濾器來增加一些驗證功能,例如客戶端的地址等。
最常用的不外乎上面幾個,至于更詳細的資料可以參考Axis解壓目錄下的docs/reference.html文件的詳細介紹。
參考資料