欢迎关注微信公众号:老夫撸代码
本期内容:老司机教你写WebService接口
什么是WebService?
WebService其实就是大家经常说的接口的一种实现方式。通过接口不同的系统可以进行数据交互。
在Json还没有流行开始之前、大家一般用webService和Socket进行数据交互,在银行、通信行业(移动、联通、电信)、政府企业等系统数据交互中应用的比较多一点。
本文主要是基于 Apache CXF进行webService的服务端以及客户端的开发。
运行环境
这里我们用Java开发webService,框架采用SpringBoot,开发工具是Intelllij IDEA.
不会Java的童鞋没有必要往下面看了!!!
第一步:搭建框架
打开IDEA分别新建2个SpringBoot项目,分别为服务端和客户端。
准备好两个项目分别启动,保证项目可以正常启动。


- 服务端的pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.2.6</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxrs</artifactId>
<version>3.2.6</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.2.6</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<version>3.2.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 客户端的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>client</name>
<description>cxf for client</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>2.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>2.7.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
从上面的pom.xml文件可以看出整个项目使用的Java8
第二步 新建WSDL文件
WSDL文件的生成一般分为2种方式
-
1、先写服务端代码,项目跑起来后WSDL文件自动生成。缺点是无法自定义WSDL、优点是傻瓜式。
-
2、根据接口规范编写WSDL文件,可以根据接口文档需求自定义相关属性。缺点是编写wsdl文件费时间而且没有服务端代码,优点是可以做一些自定义的规范。
这里我们推荐用使用2来开发,为什么呢?
因为在正规的项目中,接口文档先于代码生成,一定是先定义接口中数据的格式、长度、是否允许为空等等这些内容,而且对于报文的格式也是做了一些自定义的,因此1方法中涉及的方式是不会那样使用的。
结构图如下:

其实还有一种方式:直接使用soap进行数据交换。
流程如下:
- 接口文档编写,定义请求报文格式和响应报文格式
- 拼接xml字符串,使用post请求,通过soap协议将数据发送给服务端
- 服务端接收字符串数据后,解析xml数据
本文采用2方法进行开发,至于wsdl的语法,感兴趣的童鞋可以深入了解一下,这里我们直接贴文件。
test.wsdl
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://manzj.net/services/test/wsdl"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:s="http://manzj.net/services/test"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:tns="http://manzj.net/services/test/wsdl"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/">
<wsdl:types>
<xsd:schema targetNamespace="http://manzj.net/services/test/wsdl">
<xsd:import namespace="http://manzj.net/services/test" schemaLocation="test.xsd"></xsd:import>
</xsd:schema>
</wsdl:types>
<wsdl:message name="ReqHeader">
<wsdl:part name="ReqHeader" element="s:ReqHeader">
</wsdl:part>
</wsdl:message>
<wsdl:message name="RspHeader">
<wsdl:part name="RspHeader" element="s:RspHeader">
</wsdl:part>
</wsdl:message>
<wsdl:message name="User">
<wsdl:part name="User" element="s:User"/>
</wsdl:message>
<wsdl:message name="resUser">
<wsdl:part name="resUser" element="s:resUser"/>
</wsdl:message>
<wsdl:portType name="UserPortType">
<wsdl:operation name="getName">
<wsdl:input message="tns:User"></wsdl:input>
<wsdl:output message="tns:resUser"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="UserSoapBind" type="tns:UserPortType">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="getName">
<soap:operation soapAction="urn://getName"></soap:operation>
<wsdl:input>
<soap:header message="tns:ReqHeader" part="ReqHeader" use="literal"></soap:header>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:header message="tns:RspHeader" part="RspHeader" use="literal"></soap:header>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="myService">
<wsdl:port name="MyServerSoapEndpoint" binding="tns:UserSoapBind">
<soap:address location="http://localhost:8081/service/my"></soap:address>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
test.xsd 文件是对wsdl中的元素的类型进行定义
<?xml version="1.0" encoding="UTF-8"?>
<x:schema xmlns:x="http://www.w3.org/2001/XMLSchema"
xmlns:d="http://manzj.net/metadata"
xmlns:s="http://manzj.net/services/test"
targetNamespace="http://manzj.net/services/test" elementFormDefault="qualified"
attributeFormDefault="qualified">
<x:import namespace="http://manzj.net/metadata" schemaLocation="head.xsd"/>
<x:element name="ReqHeader" type="d:ReqHeaderType"/>
<x:element name="RspHeader" type="d:RspHeaderType"/>
<x:element name="User" type="s:UserType" />
<x:complexType name="UserType">
<x:sequence>
<x:element name="header" type="s:userHeaderType"/>
<x:element name="body" type="s:userBodyType"/>
</x:sequence>
</x:complexType>
<x:complexType name="userHeaderType">
<x:sequence>
<x:element name="from" type="x:string" />
<x:element name="to" type="x:string" />
</x:sequence>
</x:complexType>
<x:complexType name="userBodyType">
<x:sequence>
<x:element name="username" type="x:string" />
<x:element name="age" type="x:int" />
<x:element name="sex" type="x:string" />
</x:sequence>
</x:complexType>
<x:element name="resUser" type="s:resUserType" />
<x:complexType name="resUserType">
<x:sequence>
<x:element name="header" type="s:resUserHeaderType">
</x:element>
<x:element name="body" type="s:resUserBodyType">
</x:element>
</x:sequence>
</x:complexType>
<x:complexType name="resUserHeaderType">
<x:sequence>
<x:element name="from" type="x:string" />
<x:element name="to" type="x:string" />
</x:sequence>
</x:complexType>
<x:complexType name="resUserBodyType">
<x:sequence>
<x:element name="username" type="x:string" />
<x:element name="age" type="x:int" />
<x:element name="sex" type="x:string" />
</x:sequence>
</x:complexType>
</x:schema>
head.xsd对于自定义的头元素进行定义
<?xml version="1.0" encoding="UTF-8"?>
<x:schema xmlns:x="http://www.w3.org/2001/XMLSchema"
xmlns:d="http://manzj.net/metadata" targetNamespace="http://manzj.net/metadata"
elementFormDefault="qualified" attributeFormDefault="qualified">
<x:complexType name="ReqHeaderType">
<x:sequence>
<x:element name="mac" type="x:string"/>
<x:element name="ip" type="x:string"/>
<x:element name="address" type="x:string" />
<x:element name="from" type="x:string" />
<x:element name="to" type="x:string" />
</x:sequence>
</x:complexType>
<x:complexType name="RspHeaderType">
<x:sequence>
<x:element name="mac" type="x:string"/>
<x:element name="ip" type="x:string"/>
<x:element name="address" type="x:string" />
<x:element name="from" type="x:string" />
<x:element name="to" type="x:string" />
</x:sequence>
</x:complexType>
</x:schema>
上述引用顺序为 test.wsdl > test.xsd > head.xsd
xml解析:
- xmlns:s="http://manzj.net/services/test" 自定义的命名空间,引用元素的时候可以使用前缀s来代替
- xmlns:d="http://manzj.net/metadata" 自定义的命名空间,引用元素的时候可以使用前缀d来代替
- soapAction的值可以自定义
至此核心的wsdl文件已经新建好了,本文的wsdl是比较规范的定义,童鞋们可以直接应用到项目中去。
第三步 server端代码
这里我们使用wsdl2java这个工具来生成server端代码。
wsdl2java -impl -p com.example.demo.test -d . -exsh true ./test.wsdl
wsdl2java这个工具是在cxf包的bin目录下面,我们可以在官网下载apache-cxf-3.3.3后,减压并且配置环境变量,然后就可以在IDEA中的终端中使用了。
参数解释一下:
- -impl 加此参数生成server端代码
- -p 生成代码的包名
- -d . 代表在当前目录生成代码 此处可以将 . 换为具体的目录路径
- -exsh true 因为在test.wsdl中定义了soap header,如果不加这个参数生成的代码中是没有header的接收参数的。
在服务端代码生成后,我们需要在springboot中进行新建一个配置类,将此webserver发布出去,供客户端调用。
WebServiceConfig
package com.example.demo.config;
import com.example.demo.interceptors.CxfInterceptorsForOut;
import com.example.demo.test.MyServerSoapEndpointImpl;
import com.example.demo.test.MyService;
import com.example.demo.test.UserPortType;
import com.sun.xml.internal.ws.api.databinding.Databinding;
import org.apache.cxf.Bus;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.transport.servlet.CXFServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import javax.xml.ws.Endpoint;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class WebServiceConfig {
@Bean
public ServletRegistrationBean dispatcherServlet1(){
return new ServletRegistrationBean(new CXFServlet(),"/service/*");//发布服务名称
}
@Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus()
{
return new SpringBus();
}
@Bean
public UserPortType userPortType(){
return new MyServerSoapEndpointImpl();
}
@Bean
public Endpoint endpoint() {
EndpointImpl endpoint=new EndpointImpl(springBus(), userPortType());//绑定要发布的服务
endpoint.getInInterceptors().add(new LoggingInInterceptor());
endpoint.getOutInterceptors().add(new LoggingOutInterceptor());
endpoint.getOutInterceptors().add(new CxfInterceptorsForOut());
endpoint.publish("/my"); //显示要发布的名称
return endpoint;
}
}
CxfInterceptorsForOut 此类是用来修改报文格式的
package com.example.demo.interceptors;
import org.apache.cxf.attachment.AttachmentDeserializer;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.jaxb.JAXBDataBinding;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class CxfInterceptorsForOut extends AbstractPhaseInterceptor<Message> {
public CxfInterceptorsForOut(){
super(Phase.PREPARE_SEND);
}
@Override
public void handleMessage(Message message) throws Fault {
try {
Map<String, String> envMap = new HashMap<>();
envMap.put("s", "http://manzj.net/services/test");
envMap.put("d","http://manzj.net/metadata");
//在命名空间下的元素都以自定义前缀生成
Map<String, String> namespaceMap = new HashMap<>();
namespaceMap.put("http://manzj.net/services/test", "s");
namespaceMap.put("http://manzj.net/metadata", "d");
JAXBDataBinding dataBinding = (JAXBDataBinding) message.getExchange().getEndpoint().getService()
.getDataBinding();
dataBinding.setNamespaceMap(namespaceMap);
message.put("soap.env.ns.map", envMap);
message.put("disable.outputstream.optimization", true);
} catch (Exception e) {
e.printStackTrace();
}
}
}
至此服务端就已经开发完成了。


第四步 client端代码
前面我们已经得到server端的wsdl文件,
链接:http://localhost:8081/service/my?wsdl
这里我也用wsdl2java生成client端代码
wsdl2java -p com.example.client.cxf -d . -exsh true http://localhost:8081/service/my?wsdl
客户端代码生成后,就可以直接使用了,这里我们用JaxWsProxyFactoryBean来请求服务端
代码片段
try {
String address = "http://localhost:8081/service/my?wsdl";
JaxWsProxyFactoryBean jaxWsProxyFactoryBean = new JaxWsProxyFactoryBean();
jaxWsProxyFactoryBean.setServiceClass(UserPortType.class);
jaxWsProxyFactoryBean.setAddress(address);
jaxWsProxyFactoryBean.getInInterceptors().add(new LoggingInInterceptor());
jaxWsProxyFactoryBean.getOutInterceptors().add(new LoggingOutInterceptor());
jaxWsProxyFactoryBean.getOutInterceptors().add(new CSGIIOutInterceptor());
UserPortType userPortType= (UserPortType)jaxWsProxyFactoryBean.create();
UserType userType = new UserType();
UserHeaderType userHeaderType = new UserHeaderType();
userHeaderType.setFrom("100");
userHeaderType.setTo("200");
userType.setHeader(userHeaderType);
UserBodyType userBodyType = new UserBodyType();
userBodyType.setUsername("张三");
userBodyType.setSex("男");
userBodyType.setAge(28);
userType.setBody(userBodyType);
ReqHeaderType reqHeaderType = new ReqHeaderType();
reqHeaderType.setMac("11111111111111");
reqHeaderType.setIp("127.0.0.1");
reqHeaderType.setAddress("localhost:7777");
reqHeaderType.setFrom("100");
reqHeaderType.setTo("200");
javax.xml.ws.Holder<RspHeaderType> rspHeader = new Holder<>();
ResUserType resUserType = userPortType.getName(userType,reqHeaderType,rspHeader);
return "aaaa";
}catch (Exception e){
return "fail";
}
这里我们也用拦截器修改报文的格式。
最后附上请求报文和返回报文
请求报文
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:s="http://manzj.net/services/test"
xmlns:d="http://manzj.net/metadata">
<soap:Header>
<s:ReqHeader>
<d:mac>11111111111111</d:mac>
<d:ip>127.0.0.1</d:ip>
<d:address>localhost:7777</d:address>
<d:from>100</d:from>
<d:to>200</d:to>
</s:ReqHeader>
</soap:Header>
<soap:Body>
<s:User>
<s:header>
<s:from>100</s:from>
<s:to>200</s:to>
</s:header>
<s:body>
<s:username>张三</s:username>
<s:age>28</s:age>
<s:sex>男</s:sex>
</s:body>
</s:User>
</soap:Body>
</soap:Envelope>
响应报文
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:s="http://manzj.net/services/test"
xmlns:d="http://manzj.net/metadata">
<soap:Header>
<s:RspHeader>
<d:mac>222222222</d:mac>
<d:ip>127.0.0.1</d:ip>
<d:address>http://localhost:8081</d:address>
<d:from>200</d:from>
<d:to>100</d:to>
</s:RspHeader>
</soap:Header>
<soap:Body>
<s:resUser>
<s:header>
<s:from>200</s:from>
<s:to>100</s:to>
</s:header>
<s:body>
<s:username>李丽</s:username>
<s:age>30</s:age>
<s:sex>女</s:sex>
</s:body>
</s:resUser>
</soap:Body>
</soap:Envelope>
结尾
看完上述代码,如果有的童鞋看完还是一脸懵逼,可以关注微信公众号 老夫撸代码 回复 CXF代码 获取所有代码。

网友评论