[原創] Web服務部署內幕[絕對原創]


http://www.chinaunix.net 作者:simbasun??發表于:2005-04-15 01:33:25
發表評論】【查看原文】【Java討論區】【關閉

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

為了能讓web服務先跑起來,先給出一個Web服務的原型,以便于后面的討論。
我們從一個最簡單的例子開始,只給出必須的東西。

所需軟件:
1.Tomcat4.1.2
2.一個Java編譯器,jdk或JBuilder等等,這是為了編譯我們的Java源程序,于web服務無關。

所需文件:
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怎么安裝我就不說了,網上關于Tomcat安裝的文章有很多。
這六個package,從ibm和apache的網站上都可以下得到。

只需要這些,我們就可以部署自己的Web服務了。。
下面是目錄結構:
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?---所需得六個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>;
????<!--實際servlet程序,這里是AxisServlet-->;
????<servlet-class>;org.apache.axis.transport.http.AxisServlet</servlet-class>;
</servlet>;

<!--?###?定義servlet和url的對應關系-->;

<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;
??}
}
---------------------------------------------


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

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

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

點擊(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>;



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

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

瀏覽器顯示的是亂碼,我們點右鍵查看源文件,結果如下:

<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>;

這就是我們想要的結果嗎?這只是服務器端送回來的SOAP消息,不過我們想要的結果在里面。。。


為了真正調用我們的Web服務,下面給出一個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,否則編譯不會通過。
??用下面的命令行運行這個class:
??java?test?everybody
??我們會得到:How?are?you,?everybody

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



2.追根究底,我們的Web服務是怎樣跑起來的
===============================================================================================


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

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

我們知道,servlet是從javax.servlet.http.HttpServlet繼承的,在服務器端被載入JVM執行,然后向客戶端輸出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?其實是:org.apache.catalina.servlets.InvokerServlet
按類名提供小服務程序。例如,如果您調用?foo/servlet/HelloServlet,
invoker?servlet將裝入該HelloServlet(如果它在其類路徑中的話)并執行。

初看上面的web.xml,好像只給出了一個servlet映射,而沒有定義invoker?servlet。
其實,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,我們這樣定義一個web.xml,似乎更能清楚的說明問題:

<?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,都會交給org.apache.catalina.servlets.InvokerServlet來處理。
或者說,所有/servlet/*?模式的url,其實都是調用InvokerServlet這個類,而InvokerServlet本身也是
一個servlet,它也是從?HttpServlet?繼承而來的。

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


再進一步,如果不想讓?InvokerServlet?在中間“搗鬼”,我們當然可以直接定義自己的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中的如下語句就可以JSP的處理方法了,這里就不再廢話了:
....
<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>;
....


下面進入正題。


我們先來看部署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>;
????<!--實際servlet程序,這里是AxisServlet-->;
????<servlet-class>;org.apache.axis.transport.http.AxisServlet</servlet-class>;
</servlet>;

<!--?###?定義servlet和url的對應關系-->;

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

</web-app>;


所有?/services/*?模式的?url?都會交給org.apache.axis.transport.http.AxisServlet處理,
AxisServlet當然也是從HttpServlet繼承而來的。這就是為什么我們部署的Web服務在調用時都要在
服務名稱前加上?services/?了。

可以說,AxisServlet是所有Web服務調用的入口。
那么AxisServlet在接手Web服務調用后都做了哪些工作呢?

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


先來看看AxisServlet的doPost函數,這里只給出了關鍵語句及注釋:

????/**
?????*?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);//獲取客戶請求信息

engine.invoke(msgContext);?//調用客戶端請求的服務

responseMsg?=?msgContext.getResponseMessage();//得到調用的返回結果

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

這樣一來,Web服務調用的來龍去脈就大致清楚了。。。

為了高清楚前面我們的三個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
是怎樣獲得輸出結果的,再來看看AxisServlet的doGet函數,這里只給出了流程框架及注釋:

**
*?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文件中讀取所有部署的服務信息,向向客戶端列出所有部署的服務,
//包括每個服務可調用的方法。

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


而Axis怎樣找到我們所請求的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。一個service是一個目標鏈,包括請求request、內容提供者provider、響應response。
在這個例子中,我們指出service名字是sayHelloService?,provider是"java:RPC",它是axis?的標記,指示這是一個java的RPC?service,
而處理它的真正的class是org.apache.axis.providers.java.RPCProvider。

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