[原創(chuàng)] Web服務(wù)部署內(nèi)幕[絕對(duì)原創(chuàng)]


http://www.chinaunix.net 作者:simbasun??發(fā)表于:2005-04-15 01:33:25
發(fā)表評(píng)論】【查看原文】【Java討論區(qū)】【關(guān)閉

===============================================================================================

為了能讓web服務(wù)先跑起來(lái),先給出一個(gè)Web服務(wù)的原型,以便于后面的討論。
我們從一個(gè)最簡(jiǎn)單的例子開始,只給出必須的東西。

所需軟件:
1.Tomcat4.1.2
2.一個(gè)Java編譯器,jdk或JBuilder等等,這是為了編譯我們的Java源程序,于web服務(wù)無(wú)關(guān)。

所需文件:
1.sayHello.java
2.web.xml
3.server-config.xml
4.Java?Packages:?axis.jar,jaxrpc.jar,tt-bytecode.jar,wsdl4j.jar,xercesImpl.jar,xml-apis.jar

至于Tomcat怎么安裝我就不說(shuō)了,網(wǎng)上關(guān)于Tomcat安裝的文章有很多。
這六個(gè)package,從ibm和apache的網(wǎng)站上都可以下得到。

只需要這些,我們就可以部署自己的Web服務(wù)了。。
下面是目錄結(jié)構(gòu):
webapps/test/WEB-INF/web.xml
webapps/test/WEB-INF/server-config.wsdd
webapps/test/WEB-INF/classes/sayHello.class
webapps/test/WEB-INF/lib/xxx.jar?---所需得六個(gè)packages


web.xml
---------------------------------------------

<?xml?version="1.0"?encoding="ISO-8859-1"?>;
<!DOCTYPE?web-app?PUBLIC?"-//Sun?Microsystems,?Inc.//DTD?Web?Application?2.2//EN"?"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">;

<web-app>;

<servlet>;
????<servlet-name>;Axis</servlet-name>;
????<!--實(shí)際servlet程序,這里是AxisServlet-->;
????<servlet-class>;org.apache.axis.transport.http.AxisServlet</servlet-class>;
</servlet>;

<!--?###?定義servlet和url的對(duì)應(yīng)關(guān)系-->;

<servlet-mapping>;
????<servlet-name>;Axis</servlet-name>;
????<url-pattern>;/services/*</url-pattern>;
</servlet-mapping>;

</web-app>;

---------------------------------------------

server-config.wsdd
---------------------------------------------
<?xml?version="1.0"?encoding="UTF-8"?>;
<deployment?xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"?xmlns="http://xml.apache.org/axis/wsdd/">;

<handler?type="java:org.apache.axis.handlers.http.URLMapper"?name="URLMapper"/>;
??
<service?name="sayHelloService"?provider="java:RPC">;
??<parameter?name="className"?value="sayHello"/>;
??<parameter?name="allowedMethods"?value="sayHelloTo"/>;
</service>;

<transport?name="http">;
??<requestFlow>;
????<handler?type="URLMapper"/>;
??</requestFlow>;
</transport>;

</deployment>;
---------------------------------------------

sayHello.java
---------------------------------------------
public?class?sayHello
{
??public?String?sayHelloTo(String?aname)
??{
????return?"How?are?you,?"?+?aname;
??}
}
---------------------------------------------


假設(shè)ip地址192.168.0.1,端口號(hào)是80,我們輸入下面的url得到服務(wù)列表(當(dāng)然這里只有一個(gè)):
http://192.168.0.1/test/services
如果你的端口號(hào)是8080,就應(yīng)該輸入http://192.168.0.1:8080/test/services,后面同理。

瀏覽器顯示:
——————————————
|And?now...?Some?Services?|
|?sayHelloService?(wsdl)??|
|??.sayHelloTo????????????|???
——————————————

sayHelloService是我們的服務(wù)名,右側(cè)的?(wsdl)是一個(gè)鏈接指向sayHelloService的WSDL文檔,
這個(gè)文檔是由Axis自動(dòng)生成的。
sayHelloTo當(dāng)然就是我們的方法了。。。

點(diǎn)擊(wsdl)鏈接或輸入下面的url,得到WSDL:
http://192.168.0.1/test/services/sayHelloService?wsdl

瀏覽器顯示sayHelloService的WSDL文檔:

<?xml?version="1.0"?encoding="UTF-8"?>;
<wsdl:definitions?targetNamespace="http://192.168.0.1/test/services/sayHelloService/test/services/sayHelloService"?xmlns="http://schemas.xmlsoap.org/wsdl/"?xmlns:apachesoap="http://xml.apache.org/xml-soap"?xmlns:impl="http://192.168.0.1/test/services/sayHelloService/test/services/sayHelloService-impl"?xmlns:intf="http://192.168.0.1/test/services/sayHelloService/test/services/sayHelloService"?xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"?xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"?xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"?xmlns:xsd="http://www.w3.org/2001/XMLSchema">;
??<wsdl:message?name="sayHelloToResponse">;
????<wsdl:part?name="return"?type="xsd:string"/>;
??</wsdl:message>;
??<wsdl:message?name="sayHelloToRequest">;
????<wsdl:part?name="aname"?type="xsd:string"/>;
??</wsdl:message>;
??<wsdl:portType?name="sayHello">;
????<wsdl:operation?name="sayHelloTo"?parameterOrder="aname">;
??????<wsdl:input?message="intf:sayHelloToRequest"?name="sayHelloToRequest"/>;
??????<wsdl:output?message="intf:sayHelloToResponse"?name="sayHelloToResponse"/>;
????</wsdl:operation>;
??</wsdl:portType>;
??<wsdl:binding?name="sayHelloServiceSoapBinding"?type="intf:sayHello">;
????<wsdlsoap:binding?style="rpc"?transport="http://schemas.xmlsoap.org/soap/http"/>;
????<wsdl:operation?name="sayHelloTo">;
??????<wsdlsoap:operation?soapAction=""/>;
??????<wsdl:input?name="sayHelloToRequest">;
????????<wsdlsoap:body?encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"?namespace="http://192.168.0.1/test/services/sayHelloService/test/services/sayHelloService"?use="encoded"/>;
??????</wsdl:input>;
??????<wsdl:output?name="sayHelloToResponse">;
????????<wsdlsoap:body?encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"?namespace="http://192.168.0.1/test/services/sayHelloService/test/services/sayHelloService"?use="encoded"/>;
??????</wsdl:output>;
????</wsdl:operation>;
??</wsdl:binding>;
??<wsdl:service?name="sayHelloService">;
????<wsdl:port?binding="intf:sayHelloServiceSoapBinding"?name="sayHelloService">;
??????<wsdlsoap:address?location="http://192.168.0.1/test/services/sayHelloService"/>;
????</wsdl:port>;
??</wsdl:service>;
</wsdl:definitions>;



我們甚至不用客戶端,就可以查看服務(wù)是否部署成功以及獲得返回結(jié)果
用Get方法獲得soap流,我們要用下面的url:
(真正調(diào)用Web服務(wù),用的是Post方法,這個(gè)后面會(huì)講)

http://192.168.0.1/test/services/sayHelloService?method=sayHelloTo&aname=everybody

瀏覽器顯示的是亂碼,我們點(diǎn)右鍵查看源文件,結(jié)果如下:

<p>;Got?response?message</p>;
<?xml?version="1.0"?encoding="UTF-8"?>;
<soapenv:Envelope?xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"?xmlns:xsd="http://www.w3.org/2001/XMLSchema"?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">;
?<soapenv:Body>;
??<sayHelloToResponse?soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">;
???<sayHelloToReturn?xsi:type="xsd:string">;How?are?you,?everybody</sayHelloToReturn>;
??</sayHelloToResponse>;
?</soapenv:Body>;
</soapenv:Envelope>;

這就是我們想要的結(jié)果嗎?這只是服務(wù)器端送回來(lái)的SOAP消息,不過(guò)我們想要的結(jié)果在里面。。。


為了真正調(diào)用我們的Web服務(wù),下面給出一個(gè)Client:

import?org.apache.axis.client.Call;
import?org.apache.axis.client.Service;
import?javax.xml.namespace.QName;

??public?class?test
??{
????public?static?void?main(String?[]?args)
????{
??????try?{
?????????????String?endpoint?=?"http://192.168.0.1/test/services/sayHelloService";
?????????????Service??service?=?new?Service();
?????????????Call?????call????=?(Call)?service.createCall();
?????????????call.setTargetEndpointAddress(?new?java.net.URL(endpoint)?);
?????????????call.setOperationName(new?QName("http://sayHelloService",?"sayHelloTo"));
?????????????String?ret?=?(String)?call.invoke(?new?Object[]?{?args[0]?}?);
?????????????System.out.println(ret);
?????????}?catch?(Exception?e)?{
?????????????e.printStackTrace();
?????????}
????}
??}

??注意要配置好正確的classpath,確保編譯器能找的到axis.jar和jaxrpc.jar,否則編譯不會(huì)通過(guò)。
??用下面的命令行運(yùn)行這個(gè)class:
??java?test?everybody
??我們會(huì)得到:How?are?you,?everybody

??這才是我們真正想要的。。。



2.追根究底,我們的Web服務(wù)是怎樣跑起來(lái)的
===============================================================================================


前面給出的兩個(gè)配置文件web.xml和server-config.wsdd,或許不是能一下子就看懂的。

先讓我們回顧一下servlet的映射模式。

我們知道,servlet是從javax.servlet.http.HttpServlet繼承的,在服務(wù)器端被載入JVM執(zhí)行,然后向客戶端輸出html流。
servlet的web.xml文件(位于?webapps/foo/WEB-INF目錄):

<?xml?version="1.0"?encoding="UTF-8"?>;
<!DOCTYPE?web-app?PUBLIC?"-//Sun?Microsystems,?Inc.//DTD?Web?Application?2.2//EN"
?????????????????????????"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">;
<web-app>;
<servlet-mapping>;
<servlet-name>;invoker</servlet-name>;
<url-pattern>;/servlet/*</url-pattern>;
</servlet-mapping>;
</web-app>;


invoker?servlet?其實(shí)是:org.apache.catalina.servlets.InvokerServlet
按類名提供小服務(wù)程序。例如,如果您調(diào)用?foo/servlet/HelloServlet,
invoker?servlet將裝入該HelloServlet(如果它在其類路徑中的話)并執(zhí)行。

初看上面的web.xml,好像只給出了一個(gè)servlet映射,而沒有定義invoker?servlet。
其實(shí),invoker?servlet?是在tomcat的conf目錄中的web.xml中定義的::
?<servlet>;
????????<servlet-name>;invoker</servlet-name>;
????????<servlet-class>;
??????????org.apache.catalina.servlets.InvokerServlet
????????</servlet-class>;
????????<init-param>;
????????????<param-name>;debug</param-name>;
????????????<param-value>;0</param-value>;
????????</init-param>;
????????<load-on-startup>;2</load-on-startup>;
????</servlet>;

所以,如果拋開Tomcat_HOME/conf/web.xml,我們這樣定義一個(gè)web.xml,似乎更能清楚的說(shuō)明問題:

<?xml?version="1.0"?encoding="UTF-8"?>;
<!DOCTYPE?web-app?PUBLIC?"-//Sun?Microsystems,?Inc.//DTD?Web?Application?2.2//EN"
?????????????????????????"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">;
<web-app>;
<servlet-name>;MyInvoker</servlet-name>;
<servlet-class>;
org.apache.catalina.servlets.InvokerServlet
</servlet-class>;
<init-param>;
<param-name>;debug</param-name>;
<param-value>;0</param-value>;
</init-param>;
<load-on-startup>;2</load-on-startup>;
</servlet>;

<servlet-mapping>;
<servlet-name>;MyInvoker</servlet-name>;
<url-pattern>;/servlet/*</url-pattern>;
</servlet-mapping>;
</web-app>;


即所有/servlet/*?模式的url,都會(huì)交給org.apache.catalina.servlets.InvokerServlet來(lái)處理。
或者說(shuō),所有/servlet/*?模式的url,其實(shí)都是調(diào)用InvokerServlet這個(gè)類,而InvokerServlet本身也是
一個(gè)servlet,它也是從?HttpServlet?繼承而來(lái)的。

這樣,我們自己的servlet就能夠通過(guò)特定的url執(zhí)行,即?/servlet/OurServlet。
當(dāng)然,如果你高興,可以定義任何的?url?pattern,而不一定是?/servlet/*,這一點(diǎn),正如我們后面
看到的Axis處理Soap消息的方法。


再進(jìn)一步,如果不想讓?InvokerServlet?在中間“搗鬼”,我們當(dāng)然可以直接定義自己的servlet:

<?xml?version="1.0"?encoding="UTF-8"?>;
<!DOCTYPE?web-app?PUBLIC?"-//Sun?Microsystems,?Inc.//DTD?Web?Application?2.2//EN"
?????????????????????????"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">;
<web-app>;
<servlet-name>;MyInvoker2</servlet-name>;
<servlet-class>;
com.foo.MyServlet
</servlet-class>;
</servlet>;

<servlet-mapping>;
<servlet-name>;MyInvoker2</servlet-name>;
<url-pattern>;/AnyName/*</url-pattern>;
</servlet-mapping>;
</web-app>;


JSP也是一樣的道理,有了上面的分析,
看看Tomcat_HOME/conf/web.xml中的如下語(yǔ)句就可以JSP的處理方法了,這里就不再?gòu)U話了:
....
<servlet>;
????????<servlet-name>;jsp</servlet-name>;
????????<servlet-class>;org.apache.jasper.servlet.JspServlet</servlet-class>;
????????<init-param>;
????????????<param-name>;logVerbosityLevel</param-name>;
????????????<param-value>;WARNING</param-value>;
????????</init-param>;
????????<load-on-startup>;3</load-on-startup>;
</servlet>;
<servlet-mapping>;
????<servlet-name>;jsp</servlet-name>;
????<url-pattern>;*.jsp</url-pattern>;
</servlet-mapping>;
....


下面進(jìn)入正題。


我們先來(lái)看部署Web?Service的web.xml:

<?xml?version="1.0"?encoding="ISO-8859-1"?>;
<!DOCTYPE?web-app?PUBLIC?"-//Sun?Microsystems,?Inc.//DTD?Web?Application?2.2//EN"?"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">;

<web-app>;

<servlet>;
????<servlet-name>;Axis</servlet-name>;
????<!--實(shí)際servlet程序,這里是AxisServlet-->;
????<servlet-class>;org.apache.axis.transport.http.AxisServlet</servlet-class>;
</servlet>;

<!--?###?定義servlet和url的對(duì)應(yīng)關(guān)系-->;

<servlet-mapping>;
????<servlet-name>;Axis</servlet-name>;
????<url-pattern>;/services/*</url-pattern>;
</servlet-mapping>;

</web-app>;


所有?/services/*?模式的?url?都會(huì)交給org.apache.axis.transport.http.AxisServlet處理,
AxisServlet當(dāng)然也是從HttpServlet繼承而來(lái)的。這就是為什么我們部署的Web服務(wù)在調(diào)用時(shí)都要在
服務(wù)名稱前加上?services/?了。

可以說(shuō),AxisServlet是所有Web服務(wù)調(diào)用的入口。
那么AxisServlet在接手Web服務(wù)調(diào)用后都做了哪些工作呢?

客戶端用call.invoke()調(diào)用web服務(wù)用的是POST,所以入口是AxisServlet.doPost...
而不是AxisServlet.doGet...


先來(lái)看看AxisServlet的doPost函數(shù),這里只給出了關(guān)鍵語(yǔ)句及注釋:

????/**
?????*?Process?a?POST?to?the?servlet?by?handing?it?off?to?the?Axis?Engine.
?????*?Here?is?where?SOAP?messages?are?received
?????*?@param?req?posted?request
?????*?@param?res?respose
?????*?@throws?ServletException?trouble
?????*?@throws?IOException?different?trouble
?????*/
?????public?void?doPost(HttpServletRequest?req,?HttpServletResponse?res)
????????throws?ServletException,?IOException
????{
msgContext?=?createMessageContext(engine,?req,?res);//獲取客戶請(qǐng)求信息

engine.invoke(msgContext);?//調(diào)用客戶端請(qǐng)求的服務(wù)

responseMsg?=?msgContext.getResponseMessage();//得到調(diào)用的返回結(jié)果

sendResponse(getProtocolVersion(req),?contentType,?res,?responseMsg);//將結(jié)果送至客戶端
?????}

這樣一來(lái),Web服務(wù)調(diào)用的來(lái)龍去脈就大致清楚了。。。

為了高清楚前面我們的三個(gè)url
http://192.168.0.1/test/services
http://192.168.0.1/test/services/sayHelloService?wsdl
http://192.168.0.1/test/services/sayHelloService?method=sayHelloTo&aname=everybody
是怎樣獲得輸出結(jié)果的,再來(lái)看看AxisServlet的doGet函數(shù),這里只給出了流程框架及注釋:

**
*?Process?GET?requests.?Because?Axis?does?not?support?the?GET-style
*?pseudo?execution?of?SOAP?methods,?this?handler?deals?with?queries
*?of?various?kinds,?not?real?SOAP?actions.
*
*?@todo?for?secure?installations,?dont?stack?trace?on?faults
*?@param?request?request?in
*?@param?response?request?out
*?@throws?ServletException
*?@throws?IOException
*/
public?void?doGet(HttpServletRequest?req,?HttpServletResponse?res)
????????throws?ServletException,?IOException
{

//如果路徑為空,比如:http://localhost/wstk/services?或?http://localhost/wstk/services/*
if((pathInfo?==?null?||?pathInfo.equals(""))?&&?!realpath.endsWith(".jws"))

{
//從server-config.wsdd文件中讀取所有部署的服務(wù)信息,向向客戶端列出所有部署的服務(wù),
//包括每個(gè)服務(wù)可調(diào)用的方法。

}else
//如果路徑不為空,比如:http://localhost/wstk/services/sayHelloService
if(realpath?!=?null)
{
//如果請(qǐng)求wsdl,比如:http://localhost/wstk/services/sayHelloService?wsdl
if(wsdlRequested)
{
//創(chuàng)建sayHelloService的WSDL文件并傳送至客戶端
}?else
//這里是利用url調(diào)用Web服務(wù)的入口,比如http://192.168.0.1/test/services/sayHelloService?method=sayHelloTo&aname=everybody
if(req.getParameterNames().hasMoreElements())
{
//如果客戶端調(diào)用的方法正確,則Axis會(huì)調(diào)用相應(yīng)的JavaBean,并把JavaBean的返回結(jié)果
//封裝為Soap消息流返回給客戶端。
}
}
}


而Axis怎樣找到我們所請(qǐng)求的JavaBean呢?答案是server-config.wsdd文件。

server-config.wsdd

<?xml?version="1.0"?encoding="UTF-8"?>;
<deployment?xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"?xmlns="http://xml.apache.org/axis/wsdd/">;

<service?name="sayHelloService"?provider="java:RPC">;
??<parameter?name="className"?value="sayHello"/>;
??<parameter?name="allowedMethods"?value="sayHelloTo"/>;
</service>;

<handler?type="java:org.apache.axis.handlers.http.URLMapper"?name="URLMapper"/>;

<transport?name="http">;
??<requestFlow>;
????<handler?type="URLMapper"/>;
??</requestFlow>;
</transport>;

</deployment>;

WSDD是web?service?deployment?descriptor的縮寫。

最外面的<deployment>;元素指示這是WSDD,并定義了java的名字空間。

接著的?<service>;元素定義了service。一個(gè)service是一個(gè)目標(biāo)鏈,包括請(qǐng)求request、內(nèi)容提供者provider、響應(yīng)response。
在這個(gè)例子中,我們指出service名字是sayHelloService?,provider是"java:RPC",它是axis?的標(biāo)記,指示這是一個(gè)java的RPC?service,
而處理它的真正的class是org.apache.axis.providers.java.RPCProvider。

接著我們要在<parameter>;中告訴RPCProvider,它如何實(shí)例化并調(diào)用正確的class(如:com.foo.MyService)。
<parameter>;元素的className指示class名,allowedMethods告訴引擎那些共用的方法要通過(guò)soap來(lái)調(diào)用。
"*"表示所有的公共方法,我們也列出方法名字列表,可以空格或逗號(hào)分割它們。