本文主要介紹使用service方式實(shí)現Web服務(wù)、復雜類(lèi)型參數或者返回值以及面向消息/文檔的服務(wù)類(lèi)型,同時(shí)還會(huì )簡(jiǎn)單提及Web服務(wù)的會(huì )話(huà)管理以及安全問(wèn)題等等。
前段時(shí)間我的一篇文章《應用AXIS開(kāi)始Web服務(wù)之旅》介紹了如何通過(guò)AXIS這個(gè)項目來(lái)實(shí)現Web服務(wù)的功能。該文章主要介紹AXIS的結構、如何使用jws文件的方式開(kāi)發(fā)一個(gè)簡(jiǎn)單的Web服務(wù),并用了比較大的篇幅來(lái)介紹Web服務(wù)的客戶(hù)端編程,應該說(shuō)是使用AXIS開(kāi)發(fā)Web服務(wù)的入門(mén)篇,本文假設你已經(jīng)看過(guò)《應用AXIS開(kāi)始Web服務(wù)之旅》并對AXIS有一定的基礎,在這個(gè)基礎上我們將要介紹的內容有幾個(gè)方面包括使用service方式實(shí)現Web服務(wù)、復雜類(lèi)型參數或者返回值以及面向消息/文檔的服務(wù)類(lèi)型,同時(shí)還會(huì )簡(jiǎn)單提及Web服務(wù)的會(huì )話(huà)管理以及安全問(wèn)題等等。
在開(kāi)始我們的文章之前,我們還需要搭建一個(gè)環(huán)境,我們需要一個(gè)支持Web服務(wù)的web應用程序并假設名字為axis,如何建立請參照《應用AXIS開(kāi)始Web服務(wù)之旅》文章中的介紹。
使用定制發(fā)布編寫(xiě)Web服務(wù)
使用jws文件的方式編寫(xiě)Web服務(wù)具有方便、快捷的優(yōu)點(diǎn),它可以很快的將你已有的類(lèi)發(fā)布成Web服務(wù)。但是更多的時(shí)候這并不是一個(gè)好的主意,因為這種做法引發(fā)的問(wèn)題是我們必須要將已有類(lèi)的源碼發(fā)布出來(lái),因為更多的時(shí)候我們并不想這樣做;另外雖然你可以先用工具開(kāi)發(fā)并調試完畢一個(gè)java文件后再改名為jws,但是這多少有些便扭,而且并不是類(lèi)中的所有方法你都想發(fā)布成可通過(guò)Web服務(wù)來(lái)訪(fǎng)問(wèn)的,這時(shí)候你就必須將這些方法的修飾符改為不是public的,這就跟你原有的類(lèi)不同步,以后的修改將會(huì )更加的麻煩。
在這里我把定制發(fā)布方式稱(chēng)為service方式,就好像JSP的出現不會(huì )使Servlet失寵的道理一樣,有了jws,service方式還是有它的用武之地,而且是大放異彩。發(fā)布一個(gè)service方式的Web服務(wù)需要兩部分內容:類(lèi)文件以及Web服務(wù)發(fā)布描述文件。下面我們使用一個(gè)簡(jiǎn)單的例子來(lái)講述這個(gè)過(guò)程。
首先我們需要一個(gè)service類(lèi),這個(gè)類(lèi)跟普通的類(lèi)沒(méi)有任何區別,下面是我們實(shí)現一個(gè)城市便民服務(wù)的類(lèi),我們需要將CityService類(lèi)的兩個(gè)方法getZip和getTel發(fā)布成Web服務(wù),編譯該文件并把class文件拷貝到<webapp>/WEB-INF/classes對應目錄下。
Package lius.axis.demo;
/**
* 該類(lèi)實(shí)現了城市服務(wù),用于發(fā)布成Web服務(wù)
* @author Liudong
*/
public class CityService {
/**
* 獲取指定城市的郵編
* @param city
* @return
*/
public String getZip(String city) {
return "510630";
}
/**
* 獲取指定城市的長(cháng)途區號
* @param city
* @return
*/
public String getTel(String city) {
return "020";
}
}
|
程序已經(jīng)完成,下面是發(fā)布這個(gè)Web服務(wù)。打開(kāi)<webapp>/WEB-INF/server-config.wsdd如果這個(gè)文件不存在則創(chuàng )建一個(gè)新的文件,內容如下:
<?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">
<!-- 服務(wù)類(lèi)名 -->
<parameter name="className" value="lius.axis.demo.CityService"/>
<!-- 允許訪(fǎng)問(wèn)所有方法 -->
<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>
|
其中粗斜體的部分是我們服務(wù)的配置信息,啟動(dòng)Tomcat并打開(kāi)瀏覽求訪(fǎng)問(wèn)地址: http://localhost:8080/axis/services/city?wsdl ,下面是瀏覽器顯示我們Web服務(wù)的WDSL數據。
當然了,這個(gè)過(guò)程比起jws方式來(lái)說(shuō)是稍微麻煩一點(diǎn),你可以把它想象成你發(fā)布一個(gè)servlet一樣,創(chuàng )建servlet類(lèi)然后在web.xml中加入配置信息。
處理復雜類(lèi)型參數和返回值
之前我們做的演示程序都很簡(jiǎn)單,方法的參數和返回值都是簡(jiǎn)單類(lèi)型的數據,但是在實(shí)際應用過(guò)程中往往沒(méi)有這么簡(jiǎn)單。在使用面向對象的編程語(yǔ)言時(shí),我們會(huì )希望數據類(lèi)型可以是某個(gè)對象,比如我們提供一個(gè)接口用來(lái)發(fā)送短信息,那么我們希望接口的參數是一個(gè)消息對象,這個(gè)消息對象封裝了一條信息的所有內容包括發(fā)送者、接收者、發(fā)送時(shí)間、優(yōu)先級、信息內容等等,如果我們把每個(gè)內容都做成一個(gè)參數,那這個(gè)接口的參數可能會(huì )非常的多。因此封裝成對象是很有必要的。
在使用Axis來(lái)編寫(xiě)Web服務(wù)時(shí)對復雜類(lèi)型數據的處理同樣也是非常簡(jiǎn)單。Axis要求復雜類(lèi)型對象的編寫(xiě)必須符合JavaBean的規范,簡(jiǎn)單的說(shuō)就是對象的屬性是通過(guò)getter/setter方法來(lái)訪(fǎng)問(wèn)的。來(lái)看看下面這個(gè)簡(jiǎn)單的例子所輸出的WSDL信息有何特殊的地方。為了簡(jiǎn)便,我們還是使用jws來(lái)編寫(xiě),需要編寫(xiě)三個(gè)文件:sms.jws,Message.java,Response.java。
//文件名:sms.jws
import lius.axis.demo.*;
public class sms{
/**
* 短信息發(fā)送Web服務(wù)接口
*/
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ā)送回應,在這里我們做了一個(gè)對Message 類(lèi)的引用
* @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并將編譯后的類(lèi)文件拷貝到axis/WEB-INF/classes對應包的目錄下,sms.jws拷貝到axis目錄,訪(fǎng)問(wèn)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>
|
這里定義了兩個(gè)類(lèi)型Message和Response,就是我們接口的參數類(lèi)型以及返回值的類(lèi)型?,F在再使用WSDL2Java工具來(lái)生成客戶(hù)端Helper類(lèi)看看Axis幫我們做了什么?它會(huì )自動(dòng)幫我們生成兩個(gè)類(lèi)Message和Response,包名與類(lèi)名都跟我們剛才定義的一致,你可以打開(kāi)看看跟我們剛才編寫(xiě)的類(lèi)差別多大?這兩個(gè)類(lèi)添加了很多方法例如getTypeDesc、getSerializer、getDeserializer等等?,F在你就可以隨便寫(xiě)個(gè)小程序測試一下了,我們就不在累贅了。Service方式Web服務(wù)的處理跟jws是類(lèi)似的,不同在于service方式需要在server-config.wsdd添加類(lèi)型的映射配置,下面給出一個(gè)配置的示例,讀者可以根據實(shí)際情況進(jìn)行修改。
<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>
|
其他編程語(yǔ)言也都可以借助語(yǔ)言本身所附帶的工具來(lái)生成這些復雜類(lèi)型,如果你嫌麻煩的話(huà)可以使用XML來(lái)描述這些復雜類(lèi)型,這樣就簡(jiǎn)單很多。
面向消息/文檔的Web服務(wù)類(lèi)型
我們前面介紹的服務(wù)方式是基于RPC(遠程過(guò)程調用)方式,這也是Web服務(wù)最常用的方式。面向消息/文檔的的類(lèi)型跟RPC不同的是它提供了一個(gè)更底層的抽象,要求更多的編程工作??蛻?hù)端可以傳入任何的XML文檔,得到的響應不一定是SOAPEnvelope,可以返回任何它所需要的東西,甚至不返回。雖然這對開(kāi)發(fā)者來(lái)說(shuō)非常的靈活,但是這種通訊類(lèi)型在實(shí)際的應用中并不常見(jiàn)。面向消息/文檔的Web服務(wù)主要適合于下面幾種情況,比如批量處理,基于表單的數據導入,有需要返回非XML數據時(shí),Web服務(wù)器實(shí)現中要求直接訪(fǎng)問(wèn)傳輸層等等。
對于RPC類(lèi)型的服務(wù)需要在全局配置文件server-config.wsdd中設置一行<service ... provider="java:RPC">,其中RPC就是服務(wù)的方式,而對于面向消息/文檔的服務(wù)類(lèi)型那java:RPC就要替換成為Message,并且面向消息/文檔的服務(wù)類(lèi)型必須通過(guò)WSDD配置來(lái)發(fā)表。對于完成面向消息服務(wù)的類(lèi),其方法必須具有以下的格式:
public Element[] methodName(Element [] elems)
|
其中methodName為你自定義的方法名。在A(yíng)xis的目錄下可以找到MessageService.java,這就是一個(gè)完成了該類(lèi)型的服務(wù)類(lèi),該服務(wù)的在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服務(wù),對客戶(hù)端來(lái)說(shuō)都是一樣的,使用WSDL2Java來(lái)生成的客戶(hù)端Helper類(lèi)的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文檔中有一句話(huà)很有意思,對于絕大多數類(lèi)似于"我從哪里可以獲得…"的問(wèn)題,答案都在MessageContext類(lèi)中。通過(guò)MessageContext類(lèi)你可以獲取下面幾個(gè)內容,一個(gè)AxisEngine實(shí)例的引用;請求以及回應的信息;驗證信息以及對于Servlet規范中的實(shí)例引用等等。例如當我們需要客戶(hù)端的IP地址時(shí)我們可以通過(guò)下面代碼片段獲?。?/p>
/**
* 獲取客戶(hù)端請求
* @return
*/
private HttpServletRequest getRequest() throws Exception{
MessageContext context = MessageContext.getCurrentContext();
HttpServletRequest req = (HttpServletRequest)context.getProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST);
return req.getRemoteHost();
}
|
在類(lèi)HTTPConstants中,所有以MC_開(kāi)頭的常量就是你所可以獲取到的信息,例如上面通過(guò)MC_HTTP_SERVLETREQUEST獲取對應Servlet規范中的HTTP請求。更詳細的信息可以通過(guò)查詢(xún)API文檔獲取。
Web服務(wù)會(huì )話(huà)管理
在Web服務(wù)中我們可以借助HTTP以及HTTP Cookie來(lái)處理會(huì )話(huà)信息。前面我們介紹了大多數對Axis的管理都是通過(guò)MessageContext實(shí)例來(lái)完成的。下面的例子首先驗證用戶(hù)的登錄賬號與口令如果正確則在會(huì )話(huà)中保存用戶(hù)的登錄信息,并提供接口供客戶(hù)端獲取密碼。
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);
//保存用戶(hù)名與口令
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;
}
}
|
對于服務(wù)器端來(lái)講只需要通過(guò)MessageContext實(shí)例獲取Session對象即可進(jìn)行會(huì )話(huà)級的數據保存或者讀取,而對于通過(guò)Axis的WSDL2Java工具生成的客戶(hù)端來(lái)講還需要做一個(gè)特殊的設置,請看下面客戶(hù)端代碼片段。
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幫助我們自動(dòng)處理服務(wù)器返回的Cookie信息以保證會(huì )話(huà)正常工作。
保護Web服務(wù)
網(wǎng)絡(luò )的安全問(wèn)題永遠是需要最先考慮的問(wèn)題,可是怎么能讓我們的Web服務(wù)更加安全呢?為此Axis建議可以根據實(shí)際的需要采取以下的幾種方法。
- 使用HTTPS傳輸方式 該方式需要在Web服務(wù)器上進(jìn)行配置同時(shí)需要客戶(hù)端的支持。該措施有效的防止數據在網(wǎng)絡(luò )傳輸過(guò)程中被窺視。
- 重命名Axis已有的一些名字,例如AdminService、AxisServlet,刪除Axis目錄下一些無(wú)用的程序,例如happyaxis.jsp以及一些無(wú)用的jar包等。
- 通過(guò)設置axis.enableListQuery的值為false來(lái)停止AxisServlet列出所有服務(wù)的功能。
- 禁止自動(dòng)生成WSDL的功能
- 使用過(guò)濾器來(lái)增加一些驗證功能,例如客戶(hù)端的地址等。
最常用的不外乎上面幾個(gè),至于更詳細的資料可以參考Axis解壓目錄下的docs/reference.html文件的詳細介紹。 |