美文网首页
老司机教你用CXF写WebService接口

老司机教你用CXF写WebService接口

作者: 老夫撸代码 | 来源:发表于2019-08-26 01:43 被阅读0次

欢迎关注微信公众号:老夫撸代码
本期内容:老司机教你写WebService接口

什么是WebService?

WebService其实就是大家经常说的接口的一种实现方式。通过接口不同的系统可以进行数据交互。
Json还没有流行开始之前、大家一般用webServiceSocket进行数据交互,在银行、通信行业(移动、联通、电信)、政府企业等系统数据交互中应用的比较多一点。
本文主要是基于 Apache CXF进行webService的服务端以及客户端的开发。

运行环境

这里我们用Java开发webService,框架采用SpringBoot,开发工具是Intelllij IDEA.
不会Java的童鞋没有必要往下面看了!!!

第一步:搭建框架

打开IDEA分别新建2个SpringBoot项目,分别为服务端和客户端。
准备好两个项目分别启动,保证项目可以正常启动。


client.png server.png
  • 服务端的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方法中涉及的方式是不会那样使用的。
结构图如下:


jiagou.png

其实还有一种方式:直接使用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();

        }

    }
}

至此服务端就已经开发完成了。


server1.png
mywsdl.png

第四步 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代码 获取所有代码。

老夫撸代码

相关文章

网友评论

      本文标题:老司机教你用CXF写WebService接口

      本文链接:https://www.haomeiwen.com/subject/gbkasctx.html