Spring Boot 2系列(五十八):集成CXF实现Web Service详解
前言:很早之前有接触和开发过Web Service 服务,但近些年在互联网行业几乎看不到 Web Service 服务了,互联网行业几乎都采用
HTTP + JSON
对外提供数据服务。
但并不意味着 Web Service 已消失(迟早的事),一些传统垂直行业的系统仍然使用 Web Service。
例如,医院的 HIS(医院信息系),10年前的系统大把的,大量外围业务系统和服务商依赖于它。
依然在使用 Web Service 技术,个人认为有以下两点:
一是这类系统一经部署就很难更换,因为更换的成本和风险都非常高,高到难以接受,导致市场固化,新兴企业更好的技术更优的产品就难以打入该行业市场,同样意味着对于复杂的业务缺少打磨产品的市场环境。
二是提供该类系统的服务商和甲方并不会主动也没有意愿去更换系统,只要能满足业务需要,更多的是在上面迭代新的功能。
与开发新系统相比,收入固定的,但付出的成本是最低,也就没有内在的驱动力去研发新技术和新产品了。
提供垂直行业信息系统的服务商实际是不多的,行业领域内可能就那么几家,采用的技术和提供的功能都可能是相似的(可能来自某一头部企业离职人员创业开发)。
随着业务的发展,这类系统必然存在局限性的。在技术层面是没有跟上行业技术发展的,在业务层面是也难以满足新形态的业务需求。
那更换该类系统的驱动可能需要来自顶层设计,例如,提出行业新的概念,制定准入规则;或大型IT企业切入该行业市场,对行业提出新的解读并提供整套更优的解决方案,从外部提供更多的赋加值,提供全方位的支持和补贴等。
近期项目需要对接HIS,花了点时间重新研究了下 Web Service 的应用,在此做个记录
Web Service
Web Service 相关概念不做过多描述,可参考 百度百科-Web Service 。
Web Service 体系架构有三个角色:
Axis2与CXF
Apache CXF 官网: http://cxf.apache.org/
Axis2 与 CXF 区别:
Axis2 Web Service 引擎 轻量级的SOA框架,可以作为ESB(企业服力总线) Sprign 集成 Web应用 服务的管控与治理Web Service 四个注解
JDK 为 Web Service 提供了四个注解:**@WebService,@WebMethod,@WebParam,@WebResult**,位于 JDK 的 javax.jws 包下。
@WebService
原码
1 |
package javax.jws; |
name
:String,指定 Web Service 的名称,映射到 WSDL 1.1 的
wsdl:portType
的
name
,默认值为 Java 类或接口的非限定名称。
1 |
<wsdl:portType name="HelloMessageServer">....</wsdl:portType> |
serviceName
: String,指定 Web Server 的服务名(对外发布的服务名),映射到 WSDL 1.1 的
wsdl:service
的
name
。在端点接口上不允许有此成员值。
默认值为端点接口
实现类的非限定类名 + Service
。例如, HelloServiceImplService。
1 |
<wsdl:service name="HelloServiceImplService">...</wsdl:service> |
portName
:String,指定 Web Service 的端口名,映射到 WSDL 1.1 的
wsdl:port
的
name
。在端点接口上不允许有此成员值。默认值为
WebService.name+Port
。例如,HelloMessageServerPort。
1 |
<wsdl:service name="HelloServiceImplService"> |
targetNamespace
:String,指定名称空间,默认是
http://
+ 端点接口的包名倒序,映身到 wsdl 的
wsdl:definitions
和
xs:schema
标签的
targetNamespace
和
xmlns:tns
。
1 |
<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://tempuri.org" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="HelloServiceImplService" targetNamespace="http://tempuri.org"> |
endpointInterface : String,指定端点接口的全限定名。如果是没有接口,直接写实现类的,该属性不用配置。
此属性允许开发人员将 接口 与 实现 分离。如果存在此属性,则 服务端点接口 将用于确定抽象 WSDL 约定(portType 和 bindings)。
服务端点接口可以包括 JSR-181 注解 ,以定制从 Java 到 WSDL 的映射。
服务实现 bean 可以实现服务端点接口,但不是必须。
如果此成员值不存在,则从服务实现 bean 上的注释生成 Web Service 约定。如果目标环境需要服务端点接口,则将其生成到一个实现定义的包中,并具有一个实现定义的名称。
在端点接口上不允许此成员值。
wsdlLocation :String,指定 Web Service 的 WSDL 描术文档的 Web 地址(URL),可以是相对路径或绝对路径。
wsdlLocation 值的存在指示 服务实现 Bean 正在实现预定义的 WSDL 约定。 如果服务实现 bean 与此WSDL 中声明的 portType 和 bindings 不一致,则 JSR-181 工具必须提供反馈。
请注意,单个 WSDL 文件可能包含多个 portType 和多个 bindings。 服务实现 bean 上的注释确定特定的portType 和与 Web Service 对应的 bindings。
注意 :实现类上可以不添加 Webservice 注解。另 @WebService.targetNamespace 注解的使用官方还有如下说明,待深入理解:
如果 @WebService.targetNamespace 注解作用在服务端点接口上,targetNamespace 被
wsdl:portType
的
namespace
使用(并关联 XML 元素)。
如果 @WebService.targetNamespace 注解作用在服务实现的 Bean 上,没有引用服务端点接口(通过 endpointInterface 属性),targetNamespace 被
wsdl:portType
和
wsdl:service
使用(并关联 XML 元素)。
如果 @WebService.targetNamespace 注解作用在服务实现的 Bean 上,引用了服务端点接口(通过 endpointInterface 属性),targetNamespace 只被
wsdl:service
使用(并关联 XML 元素)。
@WebMethod
作用在使用了 @WebService 注解的 接口 或 实现类 中的方法上。
定义一个暴露为 Web Service 的操作方法。方法必须是 public,其参数返回值,异常必须遵循JAX-RPC 1.1 第5 节中定义的规则,方法不需要抛出 java.rmi.RemoteException 异常。
原码
1 |
package javax.jws; |
对于 SOAP 绑定,这决定了
soapAction
的值。
1 |
<soap:operation soapAction="" style="document"/> |
exclude :boolean,标记方法不暴露为 Web Service 的方法。默认值是 false,即不排除。
用于停止继承的方法作为 Web Service 的一部分暴露公开。如果指定了此属性,则不能为 @WebMethod 指定其他元素。
该成员属性不允许用在端口方法上。
operationName
:String,匹配
wsdl:operation
的
name
,默认为方法名。
1 |
<wsdl:portType name="HelloMessageServer"> |
@WebResult
1 |
<wsdl:message name="Exception"> |
原码
1 |
package javax.jws; |
name :String,返回值的名称。
如果 operation 是 rpc 风格,且
@WebResult.partName
未指定,此返回值则代表
wsdl:part
的
name
值。
如果 operation 是 document 风格,或者返回值映射到 header,则代表 XML 元素的 local name 值。
partName
:String,此返回值表示的
wsdl:part
的
name
。
仅当 operation 为 rpc 风格,或 operation 为 document 风格且参数风格为
BARE
时才使用此选项。
targetNamespace :String,该返回值的 XML 名称空间(namespace)。
仅当 operation 为 document 风格 或 返回值映射到 header 时。当
target namespace
设置为空字符串(
""
),该值表示为空名称空间(empty namespace)。
header :boolean,默认 false。如果为 true,则从消息头而不是消息正文中提取结果。
@WebParam
自定义
@WebMethod
注解作用的方法的一个参数到 Web Service message part 和 XML element 的映射。
原码
1 |
package javax.jws; |
header :boolean,默认 false。如果为 true,则从消息头而不是消息正文中提取结果。
mode :WebParam.Mode,参数的流动方向,枚举值有:IN,OUT,INOUT。
只能为符合 Holder 类型定义的参数类型指定 OUT 和 INOUT(JAX-WS 2.0 [5], section 2.3.3) 。
Holder 类型的参数必须指定为 OUT 或 INOUT。
如果 operation 是 rpc 风格的,且未指定
@WebParam.partName
,则表示
wsdl:part
的
name
。
如果 operation 是 document 风格,或者参数映射到 header,则表示 XML element 的 local name。
如果 operation 是 document 风格,参数风格是
BARE
,并且模式是 OUT 或 INOUT,则必须指定名称。
partName
:String,
wsdl:part
的
name
代表此参数。
仅当
operation
为
rpc
风格 或 operation 为
document
风格,且参数风格为
BARE
时才使用此选项。
targetNamespace :String,参数的 XML 名称空间(namespace)。
仅当 operation 是 document 风格,或参数映射到 header 时才使用。
如果 target namespace 设置为
""
,则表示空名称空间。
Web Service 配置
Bus 配置
默认 Bus 实现基于 Spring 架构,通过依赖注入,在运行时将组件串联起来。
BusFactory
负责 Bus 的创建。默认的BusFactory 是
SpringBusFactory
,对应于默认的 Bus 实现。在构造过程中,SpringBusFactory 会搜索
META-INF/cxf
(包含在 CXF 的jar中)下的所有bean配置文件。根据这些配置文件构建一个
ApplicationContext
。开发者也可以提供自己的配置文件来定制 Bus。
Bus 是 CXF 的主干。它管理扩展并充当拦截器提供者。Bus 的拦截器将被添加到在 Bus上(在其上下文中)创建的所有客户端和服务器端点的相应 入站 和 出站 消息和 错误 拦截器链中。
默认情况下,它不会向这些拦截器链类型中的任何一个提供拦截器,但是可以通过 配置文件 或 Java 代码添加拦截器。
CXF 基于 Spring Boot 的 starter (cxf-spring-boot-starter-jaxws)包的自动配置默认注册了 SpringBus Bean,所以手动注册 Bus 可以省略。
自动配置SpringBus Bean :
1 |
|
classpath:META-INF/cxf/cxf.xml
,文件位于
org.apache.cxf:cxf-core:version/META-INF/cxf/cxf.xm
1 |
|
注册 SpringBus Bean :
1 |
// 可省略 |
Interceptor 配置
CXF官方: https://cxf.apache.org/docs/interceptors.html
拦截器是 CXF 内部的基本处理单元。调用服务时,会创建并调用 InterceptorChain。每个拦截器都有机会对消息做他们想做的事。这可以包括读取它、转换它、处理标头、验证消息等。
拦截器可用于 CXF 客户端和 CXF 服务器。
CXF 中的一些拦截器示例包括:
拦截器链分为阶段。每个拦截器运行的阶段在拦截器的构造函数中声明。每个阶段可能包含许多拦截器。
InterceptorProviders :
CXF 内部的几个不同组件可以为 InterceptorChain 提供拦截器。 这些实现了 InterceptorProvider 接口:
1 |
public interface InterceptorProvider { |
要将拦截器添加到拦截器链中,需要将其添加到拦截器提供者之一。
1 |
MyInterceptor interceptor = new MyInterceptor(); |
Endpoint 配置
1 |
|
Endpoint 是个抽象类,表示一个 Web Service 端点。
Endpoints 通过内部定义的静态方式来创建, 端点 始终与一个 Binding 和 一个 实现 绑定,两者在创建端点时设置。
端点状态要么是
已发布
或
未发布
。
publish
方法可用于开始发布端点,此时端点开始接受传入的请求。
可以在端点上设置
Executor
,以便更好地控制用于调度(dispatch)的传入请求的线程。例如,可以通过
ThreadPoolExecutor
并将其注册到端点来启用具有某些参数的线程池。
可以通过端点获取
Binding
来设置处理链(handler chain)。
端点可以有一个元数据文档列表,比如 WSDL 和 XMLSchema 文档,绑定到它。在发布时,JAX-WS 实现将尽可能多地重用元数据,而不是基于实现者上的注释生成新的元数据。
EndpointImpl
是 Endpoint 端点的默认实现,端点的很多属性都靠 EndpointImpl 来设置。
注册CXF Servlet Bean
ServletRegistrationBean 作用 :
用于在 Servlet 3.0+ 容器中注册一个 CXFServlet
设置请求URL前缀映射,默认是
/services
,可以在
application.properties
配置文件中自定义
1 |
#wsdl访问地址为 http://127.0.0.1:8080/services/xxxx?wsdl |
也可以关闭前缀映射,ServletRegistrationBean 构造方法提供了控制前缀开关的参数 alwaysMapUrl,默认为 true,即使用前缀映射;可以手动配置注册 ServletRegistrationBean Bean,设置
alwaysMapUrl=false
关闭前缀映射。
1 |
public ServletRegistrationBean(T servlet, boolean alwaysMapUrl, String... urlMappings) { |
设置容器加载 Servlet 的优先顺序,必须在调用 onStartup 之前指定 Servlet。
1 |
-1 = |
ServletRegistrationBean 自动配置:
1 |
|
手动注册 ServletRegistrationBean:
1 |
|
默认前缀是
/services
,wsdl 访问地址为
http://127.0.0.1:8080/services/ws/api?wsdl,注意
/ws/api
是服务暴露的访问端点。
上面改为手动注册后,wsdl 的访问地址为 http://127.0.0.1:8080/soap/ws/api?wsdl,列出服务列表:http://127.0.0.1:8080/soap/
如果是自定义 Servlet 实现,需要在启用类添加注解:*@ServletComponentScan*。如果启动时出现错误:
1 |
not loaded because DispatcherServlet Registration found non dispatcher servlet dispatcherServlet |
可能是 springboot 与 cfx 版本不兼容。
Web Service Server
Web Service 服务端的本质是接收符合 SOAP 规范的 XML消息,解析 XML 进行业务处理,返回符合 SOAP 规范的 XML。
基于 Spring Boot + CXF 实现 Web Service 服务提供者。
添加依赖
1 |
<dependency> |
服务接口
1 |
/** |
接口实现
1 |
/** |
发布服务
1 |
package com.webservice.config; |
查看 wsdl
GET 请求: http://localhost:8080/services/ws/hello?wsdl
1 |
<wsdl:definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://tempuri.org" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" name="HelloServiceImplService" targetNamespace="http://tempuri.org"> |
elementFormDefault
WSDL 的
schema
中的
elementFormDefault
有两种值,默认是
unqualified
的,表示入参报文是不受限的。
当
elementFormDefault="qualified"
时,表示入参的 XML 报文是受限的,即入参子标签是需要带前缀。
如下,入参 input 标签
tem
就是前缀。
1 |
<SOAP-ENV:Envelope |
Spring Boot 集成的 Web Service 服务,要将 WSDL 中的
elementFormDefault
设置为
qualified
的实现方式是比较另类的。
在
暴露端点接口
的包中创建一个普通文件,文件名必须是
package-info.java
,注意不是 java 文件,然后编辑文件内容如下:
1 |
"http://tempuri.org", .xml.bind.annotation.XmlSchema(namespace = |
attributeFormDefault 设置好像不起作用。
请求接口
使用 postman 发送请求 Web Service 暴露的端口接口:
请求方式:
POST
;请求体 Body:选择
raw
类型,
XML
格式。
URL: http://localhost:8080/services/ws/hello
1 |
<SOAP-ENV:Envelope |
响应结果:
1 |
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> |
Web Service Client
添加依赖
入参实体
1 |
/** |
调用实现
Web Service 服务端的本质是接收符合 SOAP 规范的 XML消息,解析 XML 进行业务处理,返回符合 SOAP 规范的 XML。
Java 端调用 Web Servcie 可以有多种方式:
基于 CXF 的客户端调用
CXF 客户端供了调用 Web Service 的多个
invoke
方法。
1 |
Object[] invoke(QName operationName, Object... params) throws Exception; |
注意:
该
invoke
的参数入参是数组,是不含参数名的,所以数组入参的顺序必须与服务端的接口入参一致。
基于原生的 SOAP 调用 【推荐此方式调用】
其步骤主要有:获取连接工厂,通过工厂创建连接;获取报文工厂,通过工厂创建报文消息,在消息报文里添加设置报文头,正文,组装XML节点等;最后使用连接(入参消息报文和目标wsdUrl)调用远程服务。返回结果的数据是 SOAP 原生的 XML 格式数据。
SOAP 调用的底层是组装 xml 请求报文发送 POST 请求。与上面 Web Service Server 章节的【请求接口】处理一致。
使用代理类工厂的方式
该方式需要 Web Service 服务端提供接口的 jar 包供客户端引入,客户端通过代理工厂对目标接口创建一个代理实现,通过代理来接口。
不建议此方式,Web Service 服务端的实现可能是跨语言跨平台的,非 Java 语言就不支持,或服务商并不会提供这样的 jar 包。
Controller 接口:
1 |
/** |
业务接口:
1 |
/** |
业务接口实现:
1 |
package com.webservice.service.impl; |
客户端请求
-
CXF 客户端请求,使用 postman 工具发送 POST 请求,请求体是 JSON:
请求地址: http://localhost:8090/cxfInvoke
1
2
3
4
5{
"wsdUrl": "http://localhost:8080/services/ws/hello?wsdl",
"operationName": "helloMessageServer",
"paramList": ["Hello","World"]
} -
SOAP 原生调用,使用 postman 工具发送 POST 请求,请求体是 JSON:
请求地址: http://localhost:8090/soapInvoke
1
2
3
4
5
6
7
8
9
10
11{
"wsdUrl": "http://localhost:8080/services/ws/hello?wsdl",
"operationName": "helloMessageServer",
"prefix":"tem",
"qualified":true,
"namespaceURI":"http://tempuri.org",
"paramsMap": {
"input1": "Hello",
"input2": "World"
}
} -
响应结果都为:Hello,World
WebServiceExecutor工具类
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194import com.webservice.common.constants.SysConstants;
import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.apache.cxf.service.model.BindingInfo;
import org.apache.cxf.service.model.BindingOperationInfo;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.springframework.util.CollectionUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.ByteArrayOutputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
/**
* WebService操作类
*/
public class WebServiceExecutor {
/**
* 请求 WebService 服务
*
* @param operationName 接口方法名
* @param wsdUrl 接口地址
* @param xmlMap 参数是XML格式时用
* @param list 参数列表
* @return String
* @throws Exception
*/
public static String requestWebService(String operationName, String wsdUrl, HashMap<String, String> xmlMap, List<Object> list) throws Exception {
if (StringUtils.isBlank(operationName)) {
throw new Exception("异常:operationName必填参数为null!");
}
if (StringUtils.isBlank(wsdUrl)) {
throw new Exception("异常:wsdUrl必填参数为null!");
}
String result = "";
//构造参数
String xmlParam = createParamList(xmlMap);
list.add(xmlParam);
Object[] params = list.toArray();
//请求
result = invokeRemoteMethod(wsdUrl, operationName, params)[0].toString();
return result;
}
/**
* 组装XML参数
*
* @param paramMap XML参数
* @return String
* @throws Exception
*/
public static String createParamList(HashMap<String, String> paramMap) throws Exception {
String result = "";
if (!CollectionUtils.isEmpty(paramMap)) {
// 创建解析器工厂
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder db = factory.newDocumentBuilder();
Document document = db.newDocument();
// 2、创建根节点request
String xmlRoot = paramMap.get(SysConstants.XML_ROOT);
Element xml = document.createElement(xmlRoot);
//3、拼接各个子节点
for (String key : paramMap.keySet()) {
if (!key.equals(SysConstants.XML_ROOT)) {
Element paramElement = document.createElement(key);
paramElement.setTextContent(paramMap.get(key));
xml.appendChild(paramElement);
}
}
document.appendChild(xml);
//将xml转换成string
result = createXmlToString(document);
}
return result;
}
private static String createParamList(String[] params) {
String result = "";
try {
// 创建解析器工厂
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder db = factory.newDocumentBuilder();
Document document = db.newDocument();
// 2、创建根节点rss
Element request = document.createElement("request");
for (String param :
params) {
Element paramElement = document.createElement(param);
paramElement.setTextContent("?");
request.appendChild(paramElement);
}
document.appendChild(request);
//获取一个新的实例
TransformerFactory transformerFactory = TransformerFactory.newInstance();
//创建一个 Transformer,可以将 XML 文档转换成其他格式
//有异常抛出,用 try catch 捕获
Transformer transformer = transformerFactory.newTransformer();
StringWriter writer = new StringWriter();
transformer.transform(new DOMSource(document), new StreamResult(writer));
//最后将 StringWriter 转换为 字符串
//输出只有一行,是纯粹的XML内容,丢失了换行符、缩进符
// System.out.println(writer.toString());
// System.out.println(createXmlToString(document));
result = createXmlToString(document);
//截取只返回需要的部分
result = result.substring(result.indexOf("<request>"), result.length());
} catch (Exception e) {
}
return result;
}
/**
* XML文档转XML字符串
*
* @param document XML文档对象
* @return String
*/
private static String createXmlToString(Document document) {
String xmlString = null;
try {
// 创建TransformerFactory工厂对象
TransformerFactory transFactory = TransformerFactory.newInstance();
// 通过工厂对象, 创建Transformer对象
Transformer transformer = transFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
//使Xml自动换行, 并自动缩进
transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); //中间的参数网址固定写法(这里还没搞懂)
transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //是否设置缩进(indent: yes|no)
// 创建DOMSource对象并将Document加载到其中
DOMSource domSource = new DOMSource(document);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 使用Transformer的transform()方法将DOM树转换成XML
transformer.transform(domSource, new StreamResult(bos));
xmlString = bos.toString();
} catch (TransformerException e) {
e.printStackTrace();
}
return xmlString;
}
/**
* 调用远程WebService接口
*
* @param url 接口地址
* @param operationName 接口方法名
* @param parameters 接口参数列表
* @return Object[]
* @throws Exception
*/
public static Object[] invokeRemoteMethod(String url, String operationName, Object[] parameters) throws Exception {
JaxWsDynamicClientFactory dcf = JaxWsDynamicClientFactory.newInstance();
if (!url.endsWith("wsdl")) {
url += "?wsdl";
}
org.apache.cxf.endpoint.Client client = dcf.createClient(url);
HTTPConduit conduit = (HTTPConduit) client.getConduit();
HTTPClientPolicy policy = new HTTPClientPolicy();
policy.setConnectionTimeout(10 * 1000); //连接超时时间
policy.setReceiveTimeout(12 * 1000);//请求超时时间.
conduit.setClient(policy);
//处理webService接口和实现类namespace不同的情况,CXF动态客户端在处理此问题时,会报No operationName was found with the name的异常
Endpoint endpoint = client.getEndpoint();
QName opName = new QName(endpoint.getService().getName().getNamespaceURI(), operationName);
BindingInfo bindingInfo = endpoint.getEndpointInfo().getBinding();
if (bindingInfo.getOperation(opName) == null) {
for (BindingOperationInfo operationInfo : bindingInfo.getOperations()) {
if (operationName.equals(operationInfo.getName().getLocalPart())) {
opName = operationInfo.getName();
break;
}
}
}
Object[] res = null;
res = client.invoke(opName, parameters);
return res;
}
}【Demo 源码 > https://gitee.com/gxing19/spring-boot-web-service】
Web Service WSDL
相关参考