天天看點

【JAX-WS入門系列】第05章_契約優先開發及隐式聲明頭資訊

完整版見 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......
           

繼續閱讀