1.第一口就吃到餡,配置一個Web服務的最簡方法
===============================================================================================
為了能讓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來調用。
"*"表示所有的公共方法,我們也列出方法名字列表,可以空格或逗號分割它們。