完整版見 https://jadyer.github.io/2013/05/31/jaxws-build-wsdl-with-wrapped/
用戶端和服務端都是Java Project,首先列出服務端代碼
首先是我們自己編寫的//src//META-INF//wsdl//myCalculator.wsdl
<?xml version="1.0" encoding="UTF-8"?>
<!-- 編寫wsdl檔案時,可以通過以下三種封裝樣式來定義開放服務的方法 -->
<!-- 1)基于Document的wrapped方式,即所有參數都通過<element>來封裝 -->
<!-- 2)基于Document的unWrapped(Bare)方式,即<message>中傳入的是具體的參數名稱 -->
<!-- 3)基于RPC的方式 -->
<!-- 這裡示範的是采用第一種方式來編寫的,這也是比較推薦使用的方式 -->
<!-- 其中targetNamespace的作用類似于Java中的package -->
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://blog.csdn.net/jadyer"
targetNamespace="http://blog.csdn.net/jadyer"
name="CalculatorServiceImpl">
<!-- 定義WebService使用的資料類型 -->
<wsdl:types>
<!--
也可以把下面的元素element及其類型定義放到一個schema檔案中,即*.xsd
然後在這裡使用include或者import引入,二者差別是namespace
<xsd:schema targetNamespace="http://blog.csdn.net/jadyer">
<xsd:import schemaLocation="calculator.xsd" namespace="http://blog.csdn.net/jadyer"/>
<xsd:include schemaLocation="calculator.xsd"/>
</xsd:schema>
-->
<!-- 這裡targetNamespace屬性值要和上面的<wsdl:definitions xmlns:tns="" targetNamespace=""/>值一樣 -->
<xsd:schema targetNamespace="http://blog.csdn.net/jadyer">
<!-- 定義一組元素,這裡定義了兩個方法add()和minus() -->
<!-- name="add"表示定義了一個名為add的元素,type="tns:add"表示add元素的類型是tns命名空間下的'add'類型 -->
<!-- 但我們并不知道add類型是啥類型,因為并不像string是schema資料類型,是以我們就要定義一個名為add的元素類型 -->
<!-- 是以我們才在下面定義了一個<xsd:complexType name="add"> -->
<xsd:element name="add" type="tns:add"/>
<xsd:element name="addResponse" type="tns:addResponse"/>
<xsd:element name="minus" type="tns:minus"/>
<xsd:element name="minusResponse" type="tns:minusResponse"/>
<xsd:element name="licenseInfo" type="xsd:string"/>
<!--
<xsd:element name="eleCalculatorException" type="tns:CalculatorException"/>
<xsd:complexType name="CalculatorException">
<xsd:sequence>
<xsd:element name="message" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
-->
<!-- 定義元素類型 -->
<xsd:complexType name="add">
<!-- 指明a要出現在b的前面,且隻出現一次,即add(int a, int b) -->
<xsd:sequence>
<xsd:element name="a" type="xsd:int"/>
<xsd:element name="b" type="xsd:int"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="addResponse">
<!-- 如果我們想讓add(int a, int b)方法沒有傳回值的話,那麼這裡就可以直接寫成<xsd:sequence/>或不寫 -->
<xsd:sequence>
<xsd:element name="addResult" type="xsd:int"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="minus">
<xsd:sequence>
<xsd:element name="num1" type="xsd:int"/>
<xsd:element name="num2" type="xsd:int"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="minusResponse">
<xsd:sequence>
<xsd:element name="minusResult" type="xsd:int"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
</wsdl:types>
<!-- 定義操作的資料元素,可比作Java中方法的調用參數 -->
<!--
<wsdl:message name="MsgCalculatorException">
<wsdl:part name="fault" element="tns:eleCalculatorException"/>
</wsdl:message>
-->
<wsdl:message name="add">
<wsdl:part name="add" element="tns:add"/>
</wsdl:message>
<wsdl:message name="addResponse">
<wsdl:part name="addResponse" element="tns:addResponse"/>
</wsdl:message>
<wsdl:message name="minus">
<wsdl:part name="minus" element="tns:minus"/>
</wsdl:message>
<wsdl:message name="minusResponse">
<wsdl:part name="minusResponse" element="tns:minusResponse"/>
</wsdl:message>
<wsdl:message name="licenseInfo">
<wsdl:part name="licenseInfo" element="tns:licenseInfo"/>
</wsdl:message>
<!-- 待綁定的對象接口,即描述了WebService可被執行的操作以及相關的消息,其可比作Java裡的方法 -->
<wsdl:portType name="CalculatorService">
<!-- 待綁定的add()方法 -->
<wsdl:operation name="add">
<wsdl:input message="tns:add"/>
<wsdl:output message="tns:addResponse"/>
</wsdl:operation>
<!-- 待綁定的minus()方法 -->
<wsdl:operation name="minus">
<wsdl:input message="tns:minus"/>
<wsdl:output message="tns:minusResponse"/>
<!--
<wsdl:fault name="CalculatorException" message="tns:MsgCalculatorException"/>
-->
</wsdl:operation>
</wsdl:portType>
<!-- 為每個端口定義消息格式和協定細節......這裡type要按照<wsdl:portType>的name來寫 -->
<wsdl:binding name="CalculatorServiceImplPortBinding" type="tns:CalculatorService">
<!-- style屬性可取值'rpc'或'document',而transport屬性定義了要使用的SOAP協定,這裡我們使用HTTP -->
<!-- soap:binding是采用SOAP1.1版本,soap12:binding是采用SOAP1.2版本,并且是采用SOAP規範來形成HTTPRequest -->
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<!-- operation元素定義了每個端口提供的操作符 -->
<wsdl:operation name="add">
<wsdl:input>
<!-- use屬性用于指定SOAP消息的編碼規則,其值為encoded或者literal -->
<!-- literal意味着type定義遵循xml模式定義 -->
<!-- encoded參考xml中已有的應用資料,通常指的是SOAP1.1規範中的soap編碼規則,若文檔中無自定義資料,便可選擇encoded -->
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="minus">
<wsdl:input>
<soap:body use="literal"/>
<!-- 指明通路minus()方法時需添加頭資訊 -->
<soap:header use="literal" part="licenseInfo" message="tns:licenseInfo"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
<!--
<wsdl:fault name="CalculatorException">
<soap:fault name="CalculatorException" use="literal"/>
</wsdl:fault>
-->
</wsdl:operation>
</wsdl:binding>
<!-- 這裡的name值要與頂部<wsdl:definitions>标簽的name值一緻 -->
<wsdl:service name="CalculatorServiceImpl">
<!-- 這裡binding屬性要與<wsdl:binding>标簽的name值一緻,這裡的name值可以自定義 -->
<wsdl:port binding="tns:CalculatorServiceImplPortBinding" name="CalculatorServiceImplPort">
<!-- 這裡用來指定服務釋出的位址,可随意指定 -->
<soap:address location="http://127.0.0.1:8088/myCalculatorService"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
然後是SIB,即服務端接口實作類CalculatorServiceImpl.java
package net.csdn.blog.jadyer;
import javax.jws.WebService;
/**
* SIB(Service Implemention Bean)
* @see ----------------------------------------------------------------------------------------------------------
* @see 契約優先的核心
* @see 1)其實WebServices開發服務端的過程中,完全可以不寫SEI,而直接寫一個SIB,然後把服務對外釋出,用戶端就足矣正常通路
* @see 這個時候,也可以在SIB中直接使用@WebParam和@WebResult來指明釋出出去的方法中的參數名和傳回值名稱等等
* @see 2)我們寫SEI的目的就是要使用裡面配置的@WebResult等注解,而要讓SEI中的注解生效,就要在SIB中使用endpointInterface
* @see 由于SEI是由我們編寫的wsdl檔案生成的,是以它裡面的注解都是很規範的,我們在SIB中隻關注實作即可,可以了解為面向接口程式設計
* @see 是以我們在使用wsdl檔案生成服務端代碼後,就可以把除了SEI外的其它類都删掉
* @see ----------------------------------------------------------------------------------------------------------
* @see 手工編寫SIB
* @see 1)最好顯式的讓SIB和SEI的targetNamespace相同
* @see 2)當指定serviceName值時,其值應為wsdl檔案中的<wsdl:service name="CalculatorServiceImpl">标簽的name值
* @see 3)不指定serviceName值時,wsdl中的<wsdl:service name="">則應為SIBService,如CalculatorServiceImplService
* @see 否則在啟動WebService服務時,會報告下面的異常
* @see Exception in thread "main" javax.xml.ws.WebServiceException:
* @see wsdl file:/F:/Tool/Code/JavaSE/ws_contractFirst/bin/META-INF/wsdl/myCalculator.wsdl
* @see has the following services [{http://blog.csdn.net/jadyer}CalculatorServiceImpl]
* @see but not {http://blog.csdn.net/jadyer}CalculatorServiceImplService
* @see Maybe you forgot to specify a service name in @WebService/@WebServiceProvider?
* @see ----------------------------------------------------------------------------------------------------------
* @see 隐式聲明頭資訊
* @see 1)wsdl檔案的<wsdl:input>中增加<soap:header use="...." part="licenseInfo" message="...."/>
* @see 2)由于我們在wsdl檔案中聲明的是<soap:header>,是以此時wsdl生成的SEI中是找不到我們所聲明的licenseInfo的
* @see 作為服務端,可以在欲驗證頭資訊的方法中加一個licenseInfo參數,并注解@WebParam(name = "licenseInfo", header=true)
* @see 作為用戶端,調用時會發現minus()并不需要傳licenseInfo參數..是以直接調用minus(num1, num2)時,服務端收到的licenseInfo=null
* @see 是以才稱為隐式聲明頭資訊
* @see 3)在SIB中對應的方法中增加licenseInfo參數,即可進行業務邏輯驗證了(本例中隻是将頭資訊licenseInfo列印輸出)
* @see 如果SEI中的header=true沒有指明的話,那麼SIB的方法中是無法擷取頭資訊licenseInfo值的,即便用戶端傳了
* @see ----------------------------------------------------------------------------------------------------------
* @create May 20, 2013 12:46:26 AM
* @author 玄玉<http://blog.csdn.net/jadyer>
*/
@WebService(serviceName="CalculatorServiceImpl",
wsdlLocation="META-INF/wsdl/myCalculator.wsdl",
endpointInter,
targetNamespace="http://blog.csdn.net/jadyer")
public class CalculatorServiceImpl implements CalculatorService {
@Override
public int add(int a, int b) {
System.out.println("[" + a + "]+[" + b + "]=" + (a+b));
return a+b;
}
@Override
public int minus(int num1, int num2, String licenseInfo) {
System.out.println("[" + num1 + "]-[" + num2 + "]=" + (num1-num2) + ",licenseInfo=[" + licenseInfo + "]");
return num1-num2;
}
}
最後是用于釋出WebService服務的ServerApp.java
package com.jadyer.server;
import javax.xml.ws.Endpoint;
import net.csdn.blog.jadyer.CalculatorServiceImpl;
/**
* 契約優先開發及隐式聲明頭資訊
* @see -----------------------------------------------------------------------------------------------------
* @see 開發流程
* @see 1)建立\\src\\META-INF\\wsdl\\myCalculator.wsdl檔案,并編寫其内容
* @see File--New--Other--MyEclipse--Web Services--WSDL--輸入檔案名後Next
* @see 這一步保持Protocol為預設的SOAP不變,SOAP Binding Options為預設document literal不變即可
* @see 2)根據wsdl檔案生成服務端代碼(wsimport -d d:/Download/ -keep -verbose myCalculator.wsdl)
* @see 它會生成很多的代碼,而作為服務端的我們,隻需保留一個接口類(SEI)即可............如果是用戶端,就不能删了
* @see 若SEI報告ObjectFactory cannot be resolved to a type,則删除@XmlSeeAlso({ObjectFactory.class})
* @see 3)編寫實作類
* @see 在實作類上指定@WebService(serviceName="", wsdlLocation="", endpointInter, targetNamespace="")
* @see 4)釋出服務
* @see 釋出時的address可任意指定,不要求一定要與myCalculator.wsdl中的<soap:address location=""/>相同
* @see 但釋出後在浏覽器中檢視wsdl時會發現,其<soap:address location=""/>值始終與釋出時指定的address相同
* @see -----------------------------------------------------------------------------------------------------
* @create May 17, 2013 11:33:09 AM
* @author 玄玉<http://blog.csdn.net/jadyer>
*/
public class ServerApp {
public static void main(String[] args) {
Endpoint.publish("http://127.0.0.1:8088/calculatorQuery", new CalculatorServiceImpl());
}
}
OK,服務端代碼示例完畢,下面是用戶端代碼
用戶端隻有一個用于示範調用服務端的ClientApp.java
注:具體用戶端代碼由wsimport生成,詳見http://blog.csdn.net/jadyer/article/details/8692108
package com.jadyer.client;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import net.csdn.blog.jadyer.CalculatorService;
import net.csdn.blog.jadyer.CalculatorServiceImpl;
public class ClientApp {
//服務端提供服務的端口是8088,如果使用Eclipse提供的TCP/IP Monitor,則此處需将8088改為TCP/IP Monitor監聽的本地端口
private static final String wsdlLocation = "http://127.0.0.1:8088/calculatorQuery?wsdl";
//取自wsdl檔案中定義的<wsdl:definitions targetNamespace=""/>的值
private static final String nameSpace = "http://blog.csdn.net/jadyer";
//取自wsdl檔案中定義的<wsdl:service name="">的值
private static final String serviceName = "CalculatorServiceImpl";
//取自wsdl檔案中定義的<wsdl:port name="">的值
private static final String portName = "CalculatorServiceImplPort";
/**
* 隐式聲明頭資訊(使用SAAJ)
* @see 本例中,服務端提供的minus()在通路時需要提供頭資訊
* @see 如果沒提供的話,也可正常通路并接收應答,因為本例中服務端并沒有強制驗證頭資訊(隻是輸出,頂多輸出個null)
* @see 通常有三種方式可以讓用戶端通路服務端時附帶頭資訊,分别為Handler,SAAJ,代理類
*/
private static void soapInvoke() throws SOAPException, IOException{
SOAPMessage message = MessageFactory.newInstance().createMessage();
SOAPEnvelope envelope = message.getSOAPPart().getEnvelope();
SOAPBody body = envelope.getBody();
SOAPHeader header = envelope.getHeader();
if(null == header){
header = envelope.getHeader();
}
//添加頭資訊
header.addHeaderElement(new QName(nameSpace, "licenseInfo", "ns")).setValue("theClientLicenseInfo");
//添加體資訊
SOAPBodyElement sbe = body.addBodyElement(new QName(nameSpace, "minus", "ns"));
sbe.addChildElement("num1").setValue("4");
sbe.addChildElement("num2").setValue("1");
System.out.println("invoke begin......");
message.writeTo(System.out);
System.out.println("");
Service service = Service.create(new URL(wsdlLocation), new QName(nameSpace, serviceName));
Dispatch<SOAPMessage> dispatch = service.createDispatch(new QName(nameSpace, portName), SOAPMessage.class, Service.Mode.MESSAGE);
SOAPMessage respMsg = dispatch.invoke(message);
respMsg.writeTo(System.out);
System.out.println("\ninvoke end......");
}
private static void wsimportInvoke() throws MalformedURLException{
CalculatorServiceImpl csl = new CalculatorServiceImpl(new URL(wsdlLocation), new QName(nameSpace, serviceName));
CalculatorService cs = csl.getCalculatorServiceImplPort();
System.out.println(cs.add(2, 3));
System.out.println(cs.minus(2, 1));
}
public static void main(String[] args) throws SOAPException, IOException {
wsimportInvoke();
soapInvoke();
}
}
控制台輸出如下
//服務端
[2]+[3]=5
[2]-[1]=1,licenseInfo=[null]
[4]-[1]=3,licenseInfo=[theClientLicenseInfo]
//用戶端
5
1
invoke begin......
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Header><ns:licenseInfo xmlns:ns="http://blog.csdn.net/jadyer">theClientLicenseInfo</ns:licenseInfo></SOAP-ENV:Header><SOAP-ENV:Body><ns:minus xmlns:ns="http://blog.csdn.net/jadyer"><num1>4</num1><num2>1</num2></ns:minus></SOAP-ENV:Body></SOAP-ENV:Envelope>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Header/><S:Body><ns2:minusResponse xmlns:ns2="http://blog.csdn.net/jadyer"><minusResult>3</minusResult></ns2:minusResponse></S:Body></S:Envelope>
invoke end......