学习原因
在开发项目时遇到几次webservice的接口对接,项目组没有人对这个东西了解,只是根据网上的demo,编写代码才成功调用,我个人在结束vue前后端分离学习,学习一下,不希望自己以后再次遇见webservice的问题。
编写一个简单的xsd文件
我是根据spring的spring-ws项目学习,spring-ws的文档要求,要先能看懂和写出一个xsd文件,所以我先学习xsd。
xsd是xml schema的简称,是以xml技术为基础的一种标记语言技术,spring-ws说这是支持范围最广的,不是最好的。
我们都知道xml是可以自定义标签的,先从一个xml例子开始
<?xml version="1.0" encoding="UTF-8"?>
<HolidayRequest xmlns="http://mycompany.com/hr/schemas">
<Holiday>
<StartDate>2006-07-03</StartDate> <EndDate>2006-07-07</EndDate>
</Holiday>
<Employee>
<Number>42</Number>
<FirstName>Arjen</FirstName>
<LastName>Poutsma</LastName>
</Employee>
</HolidayRequest>
这是一个常规的xml例子,如果把这个例子改成xsd的,应该是这样的
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://mycompany.com/hr/schemas"
xmlns:hr="http://mycompany.com/hr/schemas">
<xs:element name="HolidayRequest">
<xs:complexType>
<xs:sequence>
<xs:element ref="hr:Holiday"/>
<xs:element ref="hr:Employee"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Holiday">
<xs:complexType>
<xs:sequence>
<xs:element ref="hr:StartDate"/>
<xs:element ref="hr:EndDate"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="StartDate" type="xs:NMTOKEN"/>
<xs:element name="EndDate" type="xs:NMTOKEN"/>
<xs:element name="Employee">
<xs:complexType>
<xs:sequence>
<xs:element ref="hr:Number"/>
<xs:element ref="hr:FirstName"/>
<xs:element ref="hr:LastName"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Number" type="xs:integer"/>
<xs:element name="FirstName" type="xs:NCName"/>
<xs:element name="LastName" type="xs:NCName"/>
</xs:schema>
这也是文档上举得例子,刚开始看的的时候比较疑惑xs是什么东西,是不是Java对象中属性就要写成xs:XXX的样子。
在xml中,xmlns是XML Namespaces的缩写,也就是命名空间的意思,命名空间的格式要求是这样的:
xmlns:<前缀>="<命名空间路径>"
所以可以知道,xs只是前缀的意思, elementFormDefault是要求这个命名空间的元素是不是一定要加前缀,其值可设置为qualified或unqualified,qualified是必须加前缀,unqualified是可以忽略前缀,一般都是必须的,防止冲突。
现在知道元素或者标签(以下使用标签)schema 、element 、complexType、sequence都是这个命名空间的标签,那命名空间的标签又是什么意思;在浏览器中打开这个命名空间url路径,结果如下:
XML Schema
15 October 2014
Table of contents
Introduction
Resources
Introduction
This document describes the XML Schema namespace. It also contains a directory of links to these related resources, using Resource Directory Description Language.
Related Resources for XML Schema
Schemas for XML Schema
DTD
XML Schema 1.1
A (non-normative) DTD XMLSchema.dtd for XML Schema. It incorporates an auxiliary DTD, datatypes.dtd.
XML Schema 1.0
A (non-normative) DTD XMLSchema.dtd for XML Schema. It incorporates an auxiliary DTD, datatypes.dtd.
XML Schema
XML Schema 1.1
An XML Schema schema document for XML Schema schema documents.
XML Schema 1.0
An XML Schema schema document for XML Schema schema documents. Last updated with release of XML Schema 2nd edition in July 2004.
Normative References
W3C XML Schema Definition Language (XSD) 1.1 Part 1: Structures, W3C XML Schema Definition Language (XSD) 1.1 Part 2: Datatypes
XML Schema Part 1: Structures (2nd Edition), XML Schema Part 2: Datatypes (2nd Edition), XML Schema Part 0: Primer (2nd Edition)
XPath and XQuery Functions and Operators 3.1 introduces a new type, xs:numeric, that is a union of xs:decimal, xs:float, and xs:double.
实际上打开的是一个w3c的网页,一个说明xsd的网页,百度百科里有一句话,能够明白的说明这个属性的作用:
用于标示命名空间的地址不会被解析器用于查找信息。其惟一的作用是赋予命名空间一个惟一的名称。不过,很多公司常常会作为指针来使用命名空间指向实际存在的网页,这个网页包含关于命名空间的信息
所以可知仅仅具有标识性的作用,能不能代开就看对方公司的要求了。
这样还是说明不了schema 、element 、complexType、sequence为什么可以用,实际上,当校验xsd的合法性时,会使用xsd的DTD文件的,在这个文件中定义了这个标签,这是xsd的DTD的链接里面定义很多标签,这四个标签就在这个DTD文件中。
现在我知道了,为什么可以用这些标签。
complexType标签额作用是决定这个元素是简单元素还是复杂元素;
<xs:element name="Number" type="xs:integer"/> --简单元素
<xs:element name="Employee"> -- 复杂元素写法一
<xs:complexType>
<xs:sequence>
<xs:element ref="hr:Number"/>
<xs:element ref="hr:FirstName"/>
<xs:element ref="hr:LastName"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Employee"> -- 复杂元素写法二 , 利于复用
<xs:complexType>
<xs:sequence>
<xs:element ref="second"/> --- 只是举例,没有加前缀
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="second">
<xs:sequence>
<xs:element ref="hr:Number"/>
<xs:element ref="hr:FirstName"/>
<xs:element ref="hr:LastName"/>
</xs:sequence>
</xs:complexType>
sequence 作用是子元素的标签是不是顺序必须一致,如果不要求顺序,可以写成这样:
<xs:complexType name="second">
<xs:all>
<xs:element ref="hr:Number"/>
<xs:element ref="hr:FirstName"/>
<xs:element ref="hr:LastName"/>
</xs:all>
</xs:complexType>
all标签也是在DTD文件中定义的,这样就不验证顺序了。
所以上面的xsd例子可以改写成这个样子:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://mycompany.com/hr/schemas"
xmlns:hr="http://mycompany.com/hr/schemas">
<xs:element name="HolidayRequest">
<xs:complexType>
<xs:sequence>
<xs:element ref="hr:Holiday"/>
<xs:element ref="hr:Employee"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="Holiday">
<xs:sequence>
<xs:element name="StartDate" type="xs:date"/>
<xs:element name="EndDate" type="xs:date"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Employee">
<xs:all>
<xs:element name="Number" type="xs:integer"/>
<xs:element name="Number" type="xs:integer"/>
<xs:element name="LastName" type="xs:string"/>
</xs:all>
</xs:complexType>
</xs:schema>
到此为止,我已经能写出一个xsd文件了,把这个文件保存为hr.xsd文件。
创建一个spring-ws项目
根据文档上的例子,执行一下命令,创建一个spring-ws项目:
mvn archetype:create -DarchetypeGroupId=org.springframework.ws \
-DarchetypeArtifactId=spring-ws-archetype \
-DarchetypeVersion= \
-DgroupId=com.mycompany.hr \
-DartifactId=holidayService
悲催的是,创建失败了,报错一堆错误,也没有搞定,只能用IDE创建项目,这是创建好的项目:
这是pom.xml文件的配置:
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.ws/spring-ws-core -->
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-ws-core</artifactId>
<version>2.4.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.ws/spring-xml -->
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-xml</artifactId>
<version>2.4.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.ws/spring-ws-security -->
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-ws-security</artifactId>
<version>2.4.0.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.ws/spring-oxm -->
<dependency>
<groupId>org.springframework.ws</groupId>
<artifactId>spring-oxm</artifactId>
<version>1.5.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jdom/jdom -->
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>2.0.2</version>
</dependency>
</dependencies>
这是web.xml的配置:
<display-name>
MyCompany HR Holiday Service
</display-name>
<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet- class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring-ws</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
这是根据文档配置spring-ws-servlet.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:sws="http://www.springframework.org/schema/web-services"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/web-services
http://www.springframework.org/schema/web-services/web-services-2.0.xsd">
<context:component-scan base-package="com.mycompany.hr"/>
<sws:annotation-driven/>
</beans>
到此为止,一个简单的spring-ws项目配置出来了
编写基本的端点和方法
配置完了,开始根据文档编写
这是我写的一个简单的端点:
package com.mycompany.hr.ws;
import com.mycompany.hr.service.HumanResourceService;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.jdom2.filter.Filters;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import javax.annotation.PostConstruct;
/**
* 假期端点
* Created by nyl
* 2017/5/31
*/
@Endpoint
public class HolidayEndpoint {
private static final String NAMESPACE_URL="http://mycompany.com/hr/schema";
private XPathExpression<Element> startDateExpression;
private XPathExpression<Element> endDateExpression;
private XPathExpression<Element> firstNameExpression;
private XPathExpression<Element> lastNameExpression;
@Autowired
private HumanResourceService humanResourceService;
@PostConstruct
private void init () {
Namespace namespace = Namespace.getNamespace("hr", NAMESPACE_URL);
XPathFactory xPathFactory = XPathFactory.instance();
startDateExpression = xPathFactory.compile("//hr:startDate", Filters.element(), null, namespace);
endDateExpression = xPathFactory.compile("//hr:endDate", Filters.element(), null, namespace);
firstNameExpression = xPathFactory.compile("//hr:First", Filters.element(), null, namespace);
lastNameExpression = xPathFactory.compile("//hr:Last", Filters.element(), null, namespace);
}
}
首先,我并没有完全按照文档的例子来写端点,没有使用构造方法注入。
然后,HumanResourceService 和 HumanResourceServiceImpl我没有看到源码,是自己根据端点内容自己写的。
HolidayEndpoint注释为@Endpoint。这将该类标记为一种特殊类型的@Component,适用于在Spring-WS中处理XML消息,并使其适合组件扫描。
这也是为什么配置组件扫描context:component-scan的原因,不配置,注解无法生效。
下面是写好的执行方法:
@Endpoint
public class HolidayEndpoint {
private static final String NAMESPACE_URL="http://mycompany.com/hr/schema";
private XPathExpression<Element> startDateExpression;
private XPathExpression<Element> endDateExpression;
private XPathExpression<Element> firstNameExpression;
private XPathExpression<Element> lastNameExpression;
@Autowired
private HumanResourceService humanResourceService;
@PostConstruct
private void init () {
Namespace namespace = Namespace.getNamespace("hr", NAMESPACE_URL);
XPathFactory xPathFactory = XPathFactory.instance();
startDateExpression = xPathFactory.compile("//hr:startDate", Filters.element(), null, namespace);
endDateExpression = xPathFactory.compile("//hr:endDate", Filters.element(), null, namespace);
firstNameExpression = xPathFactory.compile("//hr:First", Filters.element(), null, namespace);
lastNameExpression = xPathFactory.compile("//hr:Last", Filters.element(), null, namespace);
}
@PayloadRoot(namespace = NAMESPACE_URL, localPart = "HolidayRequest")
public void handleHolidayRequesr( @RequestPayload Element holidayRequest) {
String startDate = startDateExpression.evaluateFirst(holidayRequest).getText();
String endDate = endDateExpression.evaluateFirst(holidayRequest).getText();
String firstName = firstNameExpression.evaluateFirst(holidayRequest).getText();
String lastName = lastNameExpression.evaluateFirst(holidayRequest).getText();
System.out.println(startDate);
System.out.println(endDate);
System.out.println(firstName);
System.out.println(lastName);
}
}
@PayloadRoot注释告诉Spring-WS,handleHolidayRequest方法适用于处理XML消息。该方法可以处理的消息类型由注释值指示,在这种情况下,它可以处理具有HolidayRequest localPart 和http://mycompany.com/hr/schemas命名空间的XML元素;
这是和wsdl文件的port对应起来的。
修改spring-ws-servlet.xml配置,增加一下配置项:
<sws:dynamic-wsdl id="holiday" portTypeName="HumanResource" locationUri="/holidayService/" targetNamespace="http://mycompany.com/hr/definitions">
<sws:xsd location="/WEB-INF/hr.xsd"/>
</sws:dynamic-wsdl>
可以启动项目测试了。
测试
启动项目,访问路径http://localhost:8080/holiday...,出现404错误,因为之前创建过一次项目,是可以使用的,所以我怀疑是不是我这一次穿件项目的方式不对,打开我的war包,发现配置性文件,全都没有打包进来,只有class文件,因为我用的是idea,所以打开项目描述文件,看到了下面内容:
<facet type="Spring" name="Spring">
<configuration>
<fileset id="fileset" name="Spring Application Context" removed="false">
<file>file://$MODULE_DIR$/src/main/webapp/WEB-INF/spring-ws-servlet.xml</file>
</fileset>
</configuration>
</facet>
也就是说,我此次创建的项目时spring项目,但是不是web项目,所以任何路径都无法访问,于是重新创建web项目,按照上面的步骤,再来一遍,运行(新项目名称是webservicelearn),访问路径http://localhost:8089/webserv...
得到正常的结果:
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:sch="http://mycompany.com/hy/schemas" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://mycompany.com/hr/definitions" targetNamespace="http://mycompany.com/hr/definitions">
<wsdl:types>
<xs:schema xmlns:hr="http://mycompany.com/hy/schemas" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://mycompany.com/hy/schemas">
<xs:element name="HolidayRequest">
<xs:complexType>
<xs:all>
<xs:element name="Holiday" type="hr:HolidayType"/>
<xs:element name="EmployeeType" type="hr:EmployeeType"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:complexType name="HolidayType">
<xs:sequence>
<xs:element name="StartDate" type="xs:date"/>
<xs:element name="EndDate" type="xs:date"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="EmployeeType">
<xs:sequence>
<xs:element name="Number" type="xs:integer"/>
<xs:element name="FirstName" type="xs:integer"/>
<xs:element name="LastName" type="xs:integer"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
</wsdl:types>
<wsdl:message name="HolidayRequest">
<wsdl:part element="sch:HolidayRequest" name="HolidayRequest"></wsdl:part>
</wsdl:message>
<wsdl:portType name="HumanResource">
<wsdl:operation name="Holiday">
<wsdl:input message="tns:HolidayRequest" name="HolidayRequest"></wsdl:input>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="HumanResourceSoap11" type="tns:HumanResource">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="Holiday">
<soap:operation soapAction=""/>
<wsdl:input name="HolidayRequest">
<soap:body use="literal"/>
</wsdl:input>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="HumanResourceService">
<wsdl:port binding="tns:HumanResourceSoap11" name="HumanResourceSoap11">
<soap:address location="http://localhost:8089/webservicelearn/holidayService/"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
浏览器测试却是出现了wsdl的内容,但是这个wsdl是有问题的,网上大多数spring-ws的教程或文章大多止步于此,只是能在浏览器中访问出现wsdl内容,至于wsdl内容对不对,能不能通过soap ui的测试,很少有人关注。
实际上我认为这个wsdl是有问题的,无法通过soap ui的测试,也无法通过工具jar包调用。
问题是这样的,这是soap ui的请求数据:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sch="http://mycompany.com/hy/schemas">
<soapenv:Header/>
<soapenv:Body>
<sch:HolidayRequest>
<!--You may enter the following 2 items in any order-->
<sch:Holiday>
<sch:StartDate>?</sch:StartDate>
<sch:EndDate>?</sch:EndDate>
</sch:Holiday>
<sch:EmployeeType>
<sch:Number>?</sch:Number>
<sch:FirstName>?</sch:FirstName>
<sch:LastName>?</sch:LastName>
</sch:EmployeeType>
</sch:HolidayRequest>
</soapenv:Body>
</soapenv:Envelope>
而项目中解析数据的代码是:
startDateExpression = xPathFactory.compile("//hr:StartDate", Filters.element(), null, namespace);
endDateExpression = xPathFactory.compile("//hr:EndDate", Filters.element(), null, namespace);
firstNameExpression = xPathFactory.compile("//hr:FirstName", Filters.element(), null, namespace);
lastNameExpression = xPathFactory.compile("//hr:LastName", Filters.element(), null, namespace);
也就是说解析不了这个请求数据。
当然这也是我目前的理解,接下来的时间,会努力解决这个问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。