springboot-cxf框架集成webService

吐槽

我不喜欢 webService ,因为它相对于 http 协议类型的接口来说太麻烦了。即便它拥有很多种类的代码生成器。

但我们又不得不学会使用它,因为有很多老的系统依旧在使用它。需求中有时又有数据必须和他们对接。

官方文档:https://cxf.apache.org/docs/springboot.html

官方 springboot 代码示例:https://github.com/apache/cxf/blob/master/distribution/src/main/release/samples/jaxws_spring_boot/README

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version><!-- 所使用的 springboot 版本 -->
<relativePath/>
</parent>

<!-- 此处省略不重要依赖 -->

<!-- https://mvnrepository.com/artifact/org.apache.cxf/cxf-spring-boot-starter-jaxws -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.4.2</version>
<exclusions><!-- CXF uses java.util.logging by default -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- <dependency> 官方 github 代码里 有配 此处注释调也没事-->
<!-- <groupId>org.apache.cxf</groupId>-->
<!-- <artifactId>cxf-rt-features-metrics</artifactId>-->
<!-- <version>3.4.2</version>-->
<!-- </dependency>-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<!-- <version>1.6.2</version>-->
</dependency>

服务端

Hello.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.ws.RequestWrapper;
import javax.xml.ws.ResponseWrapper;

/**
* Examples code for spring boot with CXF services. Hello is the interface for
* sayHello interface. This class was generated by Apache CXF 3.1.0
* 2015-05-18T13:02:03.098-05:00 Generated source version: 3.1.0
*/
@WebService(targetNamespace = "http://platform.wzy.com/", name = "Hello")
public interface Hello {

@WebResult(name = "return", targetNamespace = "")
@RequestWrapper(localName = "sayHello",
targetNamespace = "http://platform.wzy.com/",
className = "com.wzy.platform.service.SayHello")
@WebMethod(action = "urn:SayHello")
@ResponseWrapper(localName = "sayHelloResponse",
targetNamespace = "http://platform.wzy.com/",
className = "com.wzy.platform.service.SayHelloResponse")
String sayHello(@WebParam(name = "myname", targetNamespace = "") String myname);
}
**HelloPortImpl.java**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

import com.wzy.platform.service.Hello;

import java.util.logging.Logger;

/**
* Examples code for spring boot with CXF services. HelloPortImpl is the
* implementation for Hello interface. This class was generated by Apache CXF
* 3.1.0 2015-05-18T13:02:03.098-05:00 Generated source version: 3.1.0
*/

@javax.jws.WebService(serviceName = "HelloService", portName = "HelloPort",
targetNamespace = "http://platform.wzy.com/",
endpointInterface = "com.wzy.platform.service.Hello")
public class HelloPortImpl implements Hello {

private static final Logger LOG = Logger.getLogger(HelloPortImpl.class.getName());

@Override
public java.lang.String sayHello(java.lang.String myname) {
LOG.info("Executing operation sayHello" + myname);
try {
return "Hello, Welcome to CXF Spring boot " + myname + "!!!";

} catch (java.lang.Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
}

}

CxfConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

import com.wzy.platform.service.impl.HelloPortImpl;
import org.apache.cxf.Bus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.xml.ws.Endpoint;

/**
* @ClassName CxfConfig
**/
@Configuration
public class CxfConfig {

@Autowired
private Bus bus;

@Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(bus, new HelloPortImpl(), null, null, null);
endpoint.publish("/Hello");
return endpoint;
}
}

application.properties

1
cxf.path=/wsTest

启动项目:访问地址 http://localhost:8081/wsTest/Hello?wsdl

image-20210320193126089

客户端

  • 方式一:简单 ws 客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

import org.apache.cxf.staxutils.StaxUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import java.io.StringReader;
import java.net.URL;

public class SpringbootBaseApplicationTests {

public static void main(String[] args) {

String address = "http://localhost:8081/wsTest/Hello";
String request = "<q0:sayHello xmlns:q0=\"http://platform.wzy.com/\"><myname>Elan</myname></q0:sayHello>";

StreamSource source = new StreamSource(new StringReader(request));
Service service = Service.create(new URL(address + "?wsdl"),
new QName("http://platform.wzy.com/" , "HelloService"));
Dispatch<Source> disp = service.createDispatch(new QName("http://platform.wzy.com/" , "HelloPort"),
Source.class, Service.Mode.PAYLOAD);

Source result = disp.invoke(source);
System.out.println("===================================result====");
String resultAsString = StaxUtils.toString(result);
System.out.println(resultAsString);

}
}

执行结果:

image-20210320195835068

  • 方式二:交给 spring 管理

    1. 将上边的 接口 类复制到项目(新的项目)中

    image-20210320194539840

    1. resources -> config 建立一个 cxf-client.xml 文件 内容如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <?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:jaxws="http://cxf.apache.org/jaxws"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://cxf.apache.org/jaxws http://cxf.apache.org/jaxws">

      <!-- 客户端配置 -->
      <jaxws:client id="helloClient"
      serviceClass= "com.example.springbootbase.service.Hello"
      address= "http://localhost:8081/wsTest/Hello">
      </jaxws:client>
      </beans>
    2. 建立一个配置类:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.ImportResource;
      /**
      * @ClassName CxfClient
      * @Author wuzhiyong
      * @Date 2021/3/20 16:14
      * @Version 1.0
      **/

      @Configuration
      @ImportResource("classpath:config/cxf-client.xml")
      public class CxfClient {

      }
    3. 控制器中注入 helloClient

      1
      2
      3
      4
      5
      6
      7
      @Autowired
      Hello hello;

      @GetMapping("/cxf")
      public String wxClientTest(String p){
      return hello.sayHello(p);
      }

      启动项目访问 接口响应如下:

      image-20210320195605139

      image-20210320195624153

关于报错

1
2
3
4
5
6
com.sun.xml.bind.v2.util.StackRecorder: null
at com.sun.xml.bind.v2.schemagen.XmlSchemaGenerator.write(XmlSchemaGenerator.java:425) [jaxb-runtime-2.3.3.jar:2.3.3]
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.generateSchema(JAXBContextImpl.java:799) [jaxb-runtime-2.3.3.jar:2.3.3]
at org.apache.cxf.common.jaxb.JAXBUtils.generateJaxbSchemas(JAXBUtils.java:810) [cxf-core-3.4.2.jar:3.4.2]
at org.apache.cxf.jaxb.JAXBDataBinding.generateJaxbSchemas(JAXBDataBinding.java:468) [cxf-rt-databinding-jaxb-3.4.2.jar:3.4.2]
at org.apache.cxf.jaxb.JAXBDataBinding.initialize(JAXBDataBinding.java:385) [cxf-rt-databinding-jaxb-3.4.2.jar:3.4.2]

直接无视,还没找具体什么原因,但不影响使用

补充

在实际开发中 服务端的代码(接口或实现类)并没有配置的注解 例如 targetNamespace :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@WebService(name = "hello-test")
public interface HelloEndpoint {

@WebMethod
@WebResult()
String sayHellottt();

/**
* sdf
* @param id
* @return
*/
@WebMethod
WsResult testResult0(@WebParam(name = "id") String id );

/**
* bab
* @return
*/
SysUser testUser();

}

启动服务后项目默认会把包名设定为 命名空间

image-20210320204038995

这时 客户端 如果复制过来的接口类在新项目中的包路径名不一致可能就调不通了。

那么在客户端这边 我们就要手动 配置下命名空间就可以了。

image-20210320204349986

此外: 服务端 如果返回复杂对象。那么该对象也得复制到 客户端。(有的做法是把所依赖的对象都打成一个 jar 包,服务端客户端都引用这个 jar )

参考

https://cxf.apache.org/docs/springboot.html

https://github.com/apache/cxf/blob/master/distribution/src/main/release/samples/jaxws_spring_boot/README

https://janus.blog.csdn.net/article/details/78430076

http://www.xwood.net/_site_domain_/_root/5870/5874/t_c265946.html