1.第一口就吃到餡,配置一個(gè)Web服務(wù)的最簡(jiǎ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映射,而沒(méi)有定義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ō)明問(wèn)題:
<?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)分割它們。