Red Hat Document文档整理的挺全乎的,可以参考:
在Spring Boot(2.5.4)中集成CXF发布web服务(jax-ws)
步骤1:添加Maven依赖
因为cxf starter依赖版本的问题,踩过好几个坑,大家如果遇到没有明确解决线索的问题,可以尝试用最新版本试试。
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.4.4</version>
</dependency>
步骤2:定义业务接口
实测接口层是可以去掉的
package com.etoak.ws;
import javax.jws.WebService;
@WebService(name = "Hello01", targetNamespace = "http://ws.etoak.com")
public interface IWsBusi01 {
String sayHello01();
}
步骤3:定义业务实现类
package org.etoak;
import com.etoak.ws.IWsBusi01;
import javax.jws.WebService;
/**
* Hello world!
*
*/
@WebService(serviceName = "Hello01", targetNamespace = "http://ws.etoak.com", endpointInterface = "com.etoak.ws.IWsBusi01")
public class WsBusi01Impl implements IWsBusi01 {
@Override
public String sayHello01() {
System.out.println(">>>>>>>>>sayHello01");
return "sayHello01";
}
}
步骤4:发布Web服务
@Configuration
public class WebServiceConfig {
@Autowired
private Bus bus;
@Bean
public Endpoint endpoint1() {
EndpointImpl endpoint = new EndpointImpl(bus, new WsBusi01Impl());
//Web服务名称
endpoint.publish("/Hello01");
return endpoint;
}
@Bean
public Endpoint endpoint2() {
EndpointImpl endpoint = new EndpointImpl(bus, new WsBusi02Impl());
endpoint.publish("/Hello02");
return endpoint;
}
/*@Bean (name = "cxfServlet")
public ServletRegistrationBean cxfServlet() {
return new ServletRegistrationBean(new CXFServlet(), "/webservice/*");
}
@Bean (name = Bus.DEFAULT_BUS_ID)
public SpringBus getSpringBus() {
return new SpringBus();
}
// 手动发布
@Bean
public EndpointImpl serviceEndpoint() {
EndpointImpl endpoint = null;
// 获取配置的需要发布服务的列表
// ...
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
for (迭代配置的服务列表) {
// 实现类的全限定名称
String clazzName;
// 配置的自定义服务名
String webServiceAnnotationName;
try {
Class<?> clazz = Class.forName(clazzName);
Object o = clazz.newInstance();
// 判断是否有@WebService注解
WebService webServiceAnnotation = clazz.getAnnotation(WebService.class);
if (webServiceAnnotation != null) {
if (StringUtils.hasLength(webServiceAnnotationName)) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(EndpointImpl.class);
endpoint = new EndpointImpl((SpringBus) applicationContext.getBean(Bus.DEFAULT_BUS_ID), o);
endpoint.getInInterceptors().add(authInterceptor);
endpoint.publish(webServiceAnnotationName);
defaultListableBeanFactory.registerBeanDefinition(webServiceAnnotationName, beanDefinitionBuilder.getBeanDefinition());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return endpoint;
}*/
}
步骤5:启动Spring Boot
访问:http://localhost:8080/services/Hello01?wsdl
服务发布成功
===============================================
实际上当时业务需求要稍微复杂一点,拆拆成了SpringBoot工程和业务工程,SpringBoot工程作为单独的Web应用启动,加载每一个业务工程jar包。所以在打包SpringBoot工程的时候,需要将第三方jar包分离出来,具体实现参考《关于打包jar/war_wsdhla的专栏-优快云博客》
再加上XXL,最终架构如下:
==============================================
配置文件方式:
1 增加配置文件application-cxf.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:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
<jaxws:endpoint implementor="com.xxx.XxxImpl" address="/demoServ"/>
</beans>
2 启动类读取配置文件
@ImportResource(locations = {"classpath:application-cxf.xml"})
意外的元素:将XML字符串转为实体类_xml转实体类-优快云博客
使用HttpClient请求WebService
前提条件是,服务端、客户端代码(主要是入参、出参模型类)均使用cxf工具包生成的:
服务端:wsdl2java -server -impl -encoding utf-8 xxx.wsdl
客户端:wsdl2java -client -encoding utf-8 xxx.wsdl
代码:
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.xml.soap.*;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPMessage;
import java.io.*;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 组装实体对象
Envelope envelope = new Envelope();
Body body = new Body();
body.setInputParameters(实体对象);
envelope.setBody(body);
String requestXml = YkfcTzUtils.bo2Xml(envelope);
HttpPost post = new HttpPost("http://localhost:8080/services/xxxsrv");
StringEntity entity = new StringEntity(requestXml);
post.setEntity(entity);
// 执行请求并获得响应
HttpResponse response = httpClient.execute(post);
// 打印响应状态
System.out.println("Response Code : " + response.getStatusLine().getStatusCode());
// 获取并打印响应内容
String responseBody = EntityUtils.toString(response.getEntity());
System.out.println(responseBody);
} catch (Exception e) {
log.error("出现异常:", e);
}
依赖:
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.4.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
常见异常:
1 客户端异常:
com.sun.istack.SAXException2: 由于类型 "com.etoak.xxxsrv.InputParameters" 缺少 @XmlRootElement 注释, 无法将该类型编集为元素
解决:
@XmlRootElement(name = "InputParameters")
public class InputParameters {
2 服务端异常:
org.apache.cxf.binding.soap.SoapFault: "http://xxx.com/XxxSrv", the namespace on the "InputParameters" element, is not a valid SOAP version.
解决:
客户端少了两个类
Envelope.java
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.xml.bind.annotation.*;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Envelope", propOrder = {
"Header",
"Body"
})
@XmlRootElement(name="Envelope", namespace = "http://schemas.xmlsoap.org/soap/envelope/")
public class Envelope {
@XmlElement(name = "Header", namespace = "http://schemas.xmlsoap.org/soap/envelope/")
@JsonProperty("Header")
protected Object Header;
@XmlElement(name = "Body", namespace = "http://schemas.xmlsoap.org/soap/envelope/")
@JsonProperty("Body")
protected Body Body;
public Object getHeader() {
return Header;
}
public void setHeader(Object header) {
Header = header;
}
public Body getBody() {
return Body;
}
public void setBody(Body body) {
Body = body;
}
}
Body.java
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.xml.bind.annotation.*;
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Body", propOrder = {
"InputParameters"
})
@XmlRootElement(name="Body", namespace = "http://schemas.xmlsoap.org/soap/envelope/")
public class Body {
@XmlElement(name = "InputParameters", required = true, nillable = true)
@JsonProperty("InputParameters")
protected InputParameters InputParameters;
public InputParameters getInputParameters() {
return InputParameters;
}
public void setInputParameters(InputParameters inputParameters) {
InputParameters = inputParameters;
}
}
注意:
目前还发现一个问题,客户端工程中,当Envelope和Body,与根节点(如示例中的InputParameters)不在一个package下时,会报:
Response Code : 500 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><soap:Fault><faultcode>soap:Client</faultcode><faultstring>Unexpected element InputParameters found. Expected {http://xxx.com/XxxSrv}InputParameters.</faultstring></soap:Fault></soap:Body></soap:Envelope>
很奇怪,按理说客户端类再哪个包下无所有,而且客户端工程编译,打包,运行都正常,没有看到源码中有哪处是限制了Envelope和Body包路径的,目前没有找到原因。
只能放在一个目录下。
3 服务端异常:
javax.xml.bind.UnmarshalException: 意外的元素 (uri:"", local:"SOURCESYSTEMID")。所需元素为<{http://xxx.com/MsgHeader}TOKEN>,<{http://xxx.com/MsgHeader}CURRENT_PAGE>, ... ...
org.apache.cxf.interceptor.Fault: Unexpected element InputParameters found. Expected {http://xxx.com/XxxSrv}InputParameters.
解决:
上述问题是因为在客户端相应的包路径下缺少package-info.java
@javax.xml.bind.annotation.XmlSchema(namespace = "http://xxx.com/MsgHeader", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.xxx.msgheader;
@javax.xml.bind.annotation.XmlSchema(namespace = "http://xxx.com/XxxSrv", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.xxx.xxxsrv;
4 服务端异常:
org.apache.cxf.interceptor.Fault: Unmarshalling Error: 意外的元素 (uri:"http://xxx.com/XxxSrv", local:"MSGHEADER")。所需元素为<{}INPUTCOLLECTION>,<{}MSGHEADER>
解决:
这是因为服务端相应的包路径下缺少package-info.java
5、服务端是cxf,客户端是axis2,同时报错
服务端
javax.xml.bind.UnmarshalException: 意外的元素 (uri:"", local:"InputParameters")。所需元素为<{http://xxx.com/XxxSrv}INPUTCOLLECTION>,<{http://xxx.com/XxxSrv}MSGHEADER>
客户端
org.apache.axis2.AxisFault: Unmarshalling Error: 意外的元素 (uri:"", local:"InputParameters")。所需元素为<{http://xxx.com/XxxSrv}INPUTCOLLECTION>,<{http://xxx.com/XxxSrv}MSGHEADER>
解决:
因为调用的是平台的api,最终发现组装的xml结构有问题,改正确就没有问题了。
6、客户端异常:
OutputParameters outputParameters = xml2Bo(data, OutputParameters.class);
Http响应报文解析成实体类时,报上面的异常,
javax.xml.bind.UnmarshalException: 意外的元素 (uri:"http://xxx.com/XxxSrv", local:"OutputParameters")。所需元素为(none)
解决:
@XmlRootElement(name = "OutputParameters")
public class OutputParameters {
msgheader | xxxsrv | 结果 | ||
服务端 | xxx.msgheader; | xxxsrv; | 正常 | |
客户端 | xxx.msgheader; | xxxsrv; | 正常 | |
服务端 | 当前包(客、服不一样) | 当前包(客、服不一样) | 正常 | |
客户端 | 当前包(客、服不一样) | 当前包(客、服不一样) | 正常 | |
服务端 | 当前包(客、服一样) | 当前包(客、服一样) | 正常 | |
客户端 | 当前包(客、服一样) | 当前包(客、服一样) | 正常 | |
服务端 | 任意包(客、服一样) | 任意包(客、服一样) | 正常 | |
客户端 | 任意包(客、服一样) | 任意包(客、服一样) | 正常 | |
服务端 | 任意包(客、服不一样) | 任意包(客、服不一样) | 正常 | |
客户端 | 任意包(客、服不一样) | 任意包(客、服不一样) | 正常 | |
服务端 | 存在 | 存在 | 正常 | |
客户端 | 不存在 | 不存在 | 报错 | javax.xml.bind.UnmarshalException: 意外的元素 (uri:"http://xxx.com/XxxSrv", local:"ESB_FLAG")。所需元素为<{}ESB_RETURN_MESSAGE>,<{}BIZ_SERVICE_FLAG>,<{}BIZ_RETURN_CODE>,<{}ESB_FLAG>,<{}RESPONSECOLLECTION>,<{}BIZ_RETURN_MESSAGE>,<{}ESB_RETURN_CODE>,<{}ERRORCOLLECTION>,<{}INSTANCE_ID> |
服务端 | 存在 | 存在 | 正常 | |
客户端 | 不存在 | 存在 | 正常 | |
服务端 | 不存在 | 不存在 | 正常 | |
客户端 | 不存在 | 不存在 | 正常 | |
服务端 | 不存在 | 不存在 | 正常 | |
客户端 | 不存在 | 存在 | 报错 | javax.xml.bind.UnmarshalException: 意外的元素 (uri:"", local:"ESB_FLAG")。所需元素为<{http://xxx.com/XxxSrv}ESB_RETURN_MESSAGE>,<{http:/xxx.com/XxxSrv}BIZ_SERVICE_FLAG>,<{http://xxx.com/XxxSrv}BIZ_RETURN_CODE>,<{http://xxx.com/XxxSrv}ESB_FLAG>,<{http://xxx.com/XxxSrv}RESPONSECOLLECTION>,<{http://xxx.com/XxxSrv}ERRORCOLLECTION>,<{http://xxx.com/XxxSrv}INSTANCE_ID>,<{http://xxx.com/XxxSrv}BIZ_RETURN_MESSAGE>,<{http://xxx.com/XxxSrv}ESB_RETURN_CODE> |
服务端 | 不存在 | 存在 | 正常 | |
客户端 | 不存在 | 存在 | 正常 | |
服务端 | 存在 | 不存在 | 正常 | |
客户端 | 不存在 | 不存在 | 正常 | |
服务端 | 不存在 | 不存在 | 正常 | |
客户端 | 存在 | 不存在 | 正常 |