使用 Top Down 一定要懂 XML Schema 和 Web Services Description Language。
XML Schema: http://www.w3schools.com/schema/default.asp
Web Services Description Language: http://www.w3schools.com/WSDL/default.asp
這次的 Web Service 功能是客戶端 (Web service client) 送出一個 Employee Id list 到 服務器端 (Web service provider)。
首先建立一個 WAR project,然後在 web root 目錄 加入 company.xsd 和 company.wsdl。
以下是 company.xsd:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://pro.ctlok.com/company" xmlns:tns="http://pro.ctlok.com/company"
elementFormDefault="qualified">
<xs:simpleType name="EmployeeId">
<xs:restriction base="xs:string">
<xs:length value="7" />
<xs:pattern value="E[0-9]{7}" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="EmployeeTitle">
<xs:restriction base="xs:string">
<xs:enumeration value="CEO" />
<xs:enumeration value="Manger" />
<xs:enumeration value="Supervisor" />
<xs:enumeration value="Clerk" />
</xs:restriction>
</xs:simpleType>
<xs:complexType name="EmployeeInfo">
<xs:sequence>
<xs:element name="eid" type="tns:EmployeeId"
minOccurs="0" nillable="false" />
<xs:element name="firstName" type="xs:string"
minOccurs="0" nillable="false" />
<xs:element name="lastName" type="xs:string"
minOccurs="0" nillable="false" />
<xs:element name="age" type="xs:unsignedShort"
minOccurs="0" nillable="false" />
<xs:element name="title" type="tns:EmployeeTitle"
minOccurs="0" nillable="false" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="EmployeeInfoWrapper">
<xs:sequence>
<xs:element name="employeeInfo" type="tns:EmployeeInfo"
minOccurs="0" maxOccurs="unbounded" nillable="false" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="EmployeeIdWrapper">
<xs:sequence>
<xs:element name="eid" type="tns:EmployeeId"
minOccurs="0" maxOccurs="unbounded" nillable="false" />
</xs:sequence>
</xs:complexType>
<xs:element name="EmployeeIdList" type="tns:EmployeeIdWrapper" />
<xs:element name="EmployeeInfoList" type="tns:EmployeeInfoWrapper" />
</xs:schema>
以下則是 company.wsdl:
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="Company" targetNamespace="http://pro.ctlok.com/company"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://pro.ctlok.com/company"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:types>
<xsd:schema>
<xsd:import namespace="http://pro.ctlok.com/company"
schemaLocation="company.xsd" />
</xsd:schema>
</wsdl:types>
<wsdl:message name="employeeLookupRequest">
<wsdl:part element="tns:EmployeeIdList" name="employeeIdList" />
</wsdl:message>
<wsdl:message name="employeeLookupResponse">
<wsdl:part element="tns:EmployeeInfoList" name="employeeInfoList" />
</wsdl:message>
<wsdl:portType name="employeeLookupService">
<wsdl:operation name="employeeLookup">
<wsdl:input message="tns:employeeLookupRequest" />
<wsdl:output message="tns:employeeLookupResponse" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="employeeLookupBinding" type="tns:employeeLookupService">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="employeeLookup">
<soap:operation
soapAction="http://pro.ctlok.com/company/employeeLookup" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="employeeLookupService">
<wsdl:port binding="tns:employeeLookupBinding" name="employeeLookupPort">
<soap:address location="http://localhost:9080/WebService" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
現在完成了 Web Service 的介面了,但要將 Web Service 的 Object 轉為 Java Class 才可以。幸好有工具可以替我們完成這項工作,不用一個一個手動建立。使用 JAXB compiler 幫我們將 WSDL 內用到的 Object 轉為 Java Class。在 Windows 打開 CMD 輸入以下指令:
xjc -wsdl company.wsdl -p com.ctlok.pro.ws.model
*** 如果你不能執行 xjc,請查看 System Path 有沒有加入 %JAVA_HOME%/bin 。 ***
- -wsdl 是 wsdl 檔案存放的位置
- -p 是產生出來的 Java class 所在 package
想知更多指令可參考: JAXB Using
執行指令後會看到有 6 個 .java 檔案,分別是:
- EmployeeIdWrapper
- EmployeeInfo
- EmployeeInfoWrapper
- EmployeeTitle
- ObjectFactory
- package-info
EmployeeIdWrapper:
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.3 in JDK 1.6
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2011.06.23 at 04:28:35 PM CST
//
package com.ctlok.pro.ws.model;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for EmployeeIdWrapper complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* <complexType name="EmployeeIdWrapper">
* <complexContent>
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* <sequence>
* <element name="eid" type="{http://pro.ctlok.com/company}EmployeeId" maxOccurs="unbounded" minOccurs="0"/>
* </sequence>
* </restriction>
* </complexContent>
* </complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "EmployeeIdWrapper", propOrder = {
"eid"
})
public class EmployeeIdWrapper {
protected List<String> eid;
/**
* Gets the value of the eid property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the eid property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getEid().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link String }
*
*
*/
public List<String> getEid() {
if (eid == null) {
eid = new ArrayList<String>();
}
return this.eid;
}
}
EmployeeInfo:
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.3 in JDK 1.6
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2011.06.23 at 04:28:35 PM CST
//
package com.ctlok.pro.ws.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlSchemaType;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for EmployeeInfo complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* <complexType name="EmployeeInfo">
* <complexContent>
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* <sequence>
* <element name="eid" type="{http://pro.ctlok.com/company}EmployeeId" minOccurs="0"/>
* <element name="firstName" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
* <element name="lastName" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
* <element name="age" type="{http://www.w3.org/2001/XMLSchema}unsignedShort" minOccurs="0"/>
* <element name="title" type="{http://pro.ctlok.com/company}EmployeeTitle" minOccurs="0"/>
* </sequence>
* </restriction>
* </complexContent>
* </complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "EmployeeInfo", propOrder = {
"eid",
"firstName",
"lastName",
"age",
"title"
})
public class EmployeeInfo {
protected String eid;
protected String firstName;
protected String lastName;
@XmlSchemaType(name = "unsignedShort")
protected Integer age;
protected EmployeeTitle title;
/**
* Gets the value of the eid property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getEid() {
return eid;
}
/**
* Sets the value of the eid property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setEid(String value) {
this.eid = value;
}
/**
* Gets the value of the firstName property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getFirstName() {
return firstName;
}
/**
* Sets the value of the firstName property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setFirstName(String value) {
this.firstName = value;
}
/**
* Gets the value of the lastName property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getLastName() {
return lastName;
}
/**
* Sets the value of the lastName property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setLastName(String value) {
this.lastName = value;
}
/**
* Gets the value of the age property.
*
* @return
* possible object is
* {@link Integer }
*
*/
public Integer getAge() {
return age;
}
/**
* Sets the value of the age property.
*
* @param value
* allowed object is
* {@link Integer }
*
*/
public void setAge(Integer value) {
this.age = value;
}
/**
* Gets the value of the title property.
*
* @return
* possible object is
* {@link EmployeeTitle }
*
*/
public EmployeeTitle getTitle() {
return title;
}
/**
* Sets the value of the title property.
*
* @param value
* allowed object is
* {@link EmployeeTitle }
*
*/
public void setTitle(EmployeeTitle value) {
this.title = value;
}
}
EmployeeInfoWrapper:
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.3 in JDK 1.6
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2011.06.23 at 04:28:35 PM CST
//
package com.ctlok.pro.ws.model;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for EmployeeInfoWrapper complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* <complexType name="EmployeeInfoWrapper">
* <complexContent>
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* <sequence>
* <element name="employeeInfo" type="{http://pro.ctlok.com/company}EmployeeInfo" maxOccurs="unbounded" minOccurs="0"/>
* </sequence>
* </restriction>
* </complexContent>
* </complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "EmployeeInfoWrapper", propOrder = {
"employeeInfo"
})
public class EmployeeInfoWrapper {
protected List<EmployeeInfo> employeeInfo;
/**
* Gets the value of the employeeInfo property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the employeeInfo property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getEmployeeInfo().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link EmployeeInfo }
*
*
*/
public List<EmployeeInfo> getEmployeeInfo() {
if (employeeInfo == null) {
employeeInfo = new ArrayList<EmployeeInfo>();
}
return this.employeeInfo;
}
}
EmployeeTitle:
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.3 in JDK 1.6
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2011.06.23 at 04:28:35 PM CST
//
package com.ctlok.pro.ws.model;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for EmployeeTitle.
*
* <p>The following schema fragment specifies the expected content contained within this class.
* <p>
* <pre>
* <simpleType name="EmployeeTitle">
* <restriction base="{http://www.w3.org/2001/XMLSchema}string">
* <enumeration value="CEO"/>
* <enumeration value="Manger"/>
* <enumeration value="Supervisor"/>
* <enumeration value="Clerk"/>
* </restriction>
* </simpleType>
* </pre>
*
*/
@XmlType(name = "EmployeeTitle")
@XmlEnum
public enum EmployeeTitle {
CEO("CEO"),
@XmlEnumValue("Manger")
MANGER("Manger"),
@XmlEnumValue("Supervisor")
SUPERVISOR("Supervisor"),
@XmlEnumValue("Clerk")
CLERK("Clerk");
private final String value;
EmployeeTitle(String v) {
value = v;
}
public String value() {
return value;
}
public static EmployeeTitle fromValue(String v) {
for (EmployeeTitle c: EmployeeTitle.values()) {
if (c.value.equals(v)) {
return c;
}
}
throw new IllegalArgumentException(v);
}
}
ObjectFactory:
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.3 in JDK 1.6
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2011.06.23 at 04:28:35 PM CST
//
package com.ctlok.pro.ws.model;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.namespace.QName;
/**
* This object contains factory methods for each
* Java content interface and Java element interface
* generated in the com.ctlok.pro.ws.model package.
* <p>An ObjectFactory allows you to programatically
* construct new instances of the Java representation
* for XML content. The Java representation of XML
* content can consist of schema derived interfaces
* and classes representing the binding of schema
* type definitions, element declarations and model
* groups. Factory methods for each of these are
* provided in this class.
*
*/
@XmlRegistry
public class ObjectFactory {
private final static QName _EmployeeIdList_QNAME = new QName("http://pro.ctlok.com/company", "EmployeeIdList");
private final static QName _EmployeeInfoList_QNAME = new QName("http://pro.ctlok.com/company", "EmployeeInfoList");
/**
* Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.ctlok.pro.ws.model
*
*/
public ObjectFactory() {
}
/**
* Create an instance of {@link EmployeeIdWrapper }
*
*/
public EmployeeIdWrapper createEmployeeIdWrapper() {
return new EmployeeIdWrapper();
}
/**
* Create an instance of {@link EmployeeInfoWrapper }
*
*/
public EmployeeInfoWrapper createEmployeeInfoWrapper() {
return new EmployeeInfoWrapper();
}
/**
* Create an instance of {@link EmployeeInfo }
*
*/
public EmployeeInfo createEmployeeInfo() {
return new EmployeeInfo();
}
/**
* Create an instance of {@link JAXBElement }{@code <}{@link EmployeeIdWrapper }{@code >}}
*
*/
@XmlElementDecl(namespace = "http://pro.ctlok.com/company", name = "EmployeeIdList")
public JAXBElement<EmployeeIdWrapper> createEmployeeIdList(EmployeeIdWrapper value) {
return new JAXBElement<EmployeeIdWrapper>(_EmployeeIdList_QNAME, EmployeeIdWrapper.class, null, value);
}
/**
* Create an instance of {@link JAXBElement }{@code <}{@link EmployeeInfoWrapper }{@code >}}
*
*/
@XmlElementDecl(namespace = "http://pro.ctlok.com/company", name = "EmployeeInfoList")
public JAXBElement<EmployeeInfoWrapper> createEmployeeInfoList(EmployeeInfoWrapper value) {
return new JAXBElement<EmployeeInfoWrapper>(_EmployeeInfoList_QNAME, EmployeeInfoWrapper.class, null, value);
}
}
package-info:
//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, vJAXB 2.1.3 in JDK 1.6
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a>
// Any modifications to this file will be lost upon recompilation of the source schema.
// Generated on: 2011.06.23 at 04:28:35 PM CST
//
@javax.xml.bind.annotation.XmlSchema(namespace = "http://pro.ctlok.com/company", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.ctlok.pro.ws.model;
將這些 Java Class 複製到 WAR project 內。
再建立一個 Java Class - EmployeeLookupService:
package com.ctlok.pro.ws;
import java.util.HashMap;
import java.util.Map;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.xml.bind.annotation.XmlSeeAlso;
import com.ctlok.pro.ws.model.EmployeeIdWrapper;
import com.ctlok.pro.ws.model.EmployeeInfo;
import com.ctlok.pro.ws.model.EmployeeInfoWrapper;
import com.ctlok.pro.ws.model.EmployeeTitle;
import com.ctlok.pro.ws.model.ObjectFactory;
@WebService(
name = "employeeLookupService",
serviceName = "employeeLookupService",
portName = "employeeLookupPort",
targetNamespace= "http://pro.ctlok.com/company",
wsdlLocation = "company.wsdl")
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
@XmlSeeAlso({ObjectFactory.class})
public class EmployeeLookupService {
private Map<String, EmployeeInfo> infoMap;
public EmployeeLookupService(){
infoMap = new HashMap<String, EmployeeInfo>();
EmployeeInfo info1 = new EmployeeInfo();
info1.setEid("E1000000");
info1.setFirstName("Lawrence");
info1.setLastName("Cheung");
info1.setAge(24);
info1.setTitle(EmployeeTitle.CEO);
EmployeeInfo info2 = new EmployeeInfo();
info2.setEid("E1524125");
info2.setFirstName("Tom");
info2.setLastName("Wong");
info2.setAge(22);
info2.setTitle(EmployeeTitle.CLERK);
EmployeeInfo info3 = new EmployeeInfo();
info3.setEid("E7452145");
info3.setFirstName("John");
info3.setLastName("Lee");
info3.setAge(29);
info3.setTitle(EmployeeTitle.MANGER);
EmployeeInfo info4 = new EmployeeInfo();
info4.setEid("E6523547");
info4.setFirstName("Katty");
info4.setLastName("Choi");
info4.setAge(24);
info4.setTitle(EmployeeTitle.SUPERVISOR);
infoMap.put(info1.getEid(), info1);
infoMap.put(info2.getEid(), info2);
infoMap.put(info3.getEid(), info3);
infoMap.put(info4.getEid(), info4);
}
@WebMethod(operationName="employeeLookup")
@WebResult(name = "EmployeeInfoList", partName = "employeeInfoList")
public EmployeeInfoWrapper employeeLookup(
@WebParam(name = "EmployeeIdList", partName = "employeeIdList")
EmployeeIdWrapper employeeIdWrapper){
EmployeeInfoWrapper employeeInfoWrapper = new EmployeeInfoWrapper();
for (String eid: employeeIdWrapper.getEid()){
EmployeeInfo info = infoMap.get(eid);
if (info == null)
info = new EmployeeInfo();
employeeInfoWrapper.getEmployeeInfo().add(info);
}
return employeeInfoWrapper;
}
}
完成了 Web Service Provider。
@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
以上這句注解很重要,預設是 WRAPPED,即是 JAX-WS 會替你自動包裝好 List Object,但我們在 XML Schema 已經設定了 Wrapper,所以不用設定為 WRAPPED。
————– 分隔線 ————–
@XmlSeeAlso({ObjectFactory.class})
以上的注解則是確保 JAXB 正常運作,由 XML 轉為 Object 或 Object 轉為 XML。
————– 分隔線 ————–
@WebResult(name = "EmployeeInfoList", partName = "employeeInfoList")
以上的注解對應 WSDL 內的:
<wsdl:message name="employeeLookupRequest">
<wsdl:part element="tns:EmployeeIdList" name="employeeIdList" />
</wsdl:message>
————– 分隔線 ————–
@WebResult(name = "EmployeeInfoList", partName = "employeeInfoList")
以上的注解則對應 WSDL 內的:
<wsdl:message name="employeeLookupResponse">
<wsdl:part element="tns:EmployeeInfoList" name="employeeInfoList" />
</wsdl:message>
現在將 WAR project 放上 Application Server 並運行 http://localhost/employeeLookupService?wsdl。如果發現找不到 wsdl 檔案即是 wsdl 放的位置不正確。
成功找到後可以使用 soapUI 去測試一下,傳送以下 SOAP XML 到 Server:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:com="http://pro.ctlok.com/company">
<soapenv:Header/>
<soapenv:Body>
<com:EmployeeIdList>
<!--Zero or more repetitions:-->
<com:eid>E1000000</com:eid>
<com:eid>E1524125</com:eid>
</com:EmployeeIdList>
</soapenv:Body>
</soapenv:Envelope>
Server 回傳以下 XML 即代表成功設定:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<EmployeeInfoList xmlns="http://pro.ctlok.com/company">
<employeeInfo>
<eid>E1000000</eid>
<firstName>Lawrence</firstName>
<lastName>Cheung</lastName>
<age>24</age>
<title>CEO</title>
</employeeInfo>
<employeeInfo>
<eid>E1524125</eid>
<firstName>Tom</firstName>
<lastName>Wong</lastName>
<age>22</age>
<title>Clerk</title>
</employeeInfo>
</EmployeeInfoList>
</soapenv:Body>
</soapenv:Envelope>
有時間再講解一下 Java Web Service Client 如何設定。
相閞書籍: