一、导入pom依赖
org.apache.cxf
cxf-spring-boot-starter-jaxws
3.4.0
说明:本文章记录的是基于springboot搭建的cxf服务端和soap客户端调用
二、创建webservice服务端接口
package cn.lyn.webservice.serviceone;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
/**
* webService服务端接口
* @author LengYouNuan
* @create 2021-05-23 下午5:47
*/
@WebService
//@XmlType(name = "TypeName")
//@BindingType(value = SOAPBinding.SOAP12HTTP_MTOM_BINDING)
public interface WebServiceInterface {
@WebMethod
//WebResult注解指定返回值 不使用该注解可能导致获取不到返回值
@WebResult(name="sayHelloResponse",targetNamespace = "http://serviceone.webservice.lyn.cn/")
public String sayHello(@WebParam(name="name",targetNamespace = "http://serviceone.webservice.lyn.cn/")String name);
}
说明:目前我使用到了四个注解,分别是
@WebService:表明该接口是一个webservice服务接口,注解里的属性值可以设置生成的wsdl文档的命名空间等信息,此处没有遇到相关错误,因此没有进行设置,不过建议还是设置上。
@WebMethod:表明该方法是服务要发布的方法,对应属性也可以设置上
@WebResult:设置方法返回值,此处最开始没有设置导致获取不到返回值,因此务必设置上
@WebParam:设置参数信息,指定参数名字和参数所属命名空间,必须设置,不然可能导致参数不能传递进来
三、创建实现类 实现具体的业务
package cn.lyn.webservice.serviceone.impl;
import cn.lyn.webservice.serviceone.WebServiceInterface;
import javax.jws.WebService;
/**
* @author LengYouNuan
* @create 2021-05-23 下午7:17
*/
@WebService
public class WebServiceImpl implements WebServiceInterface {
public String sayHello(String name) {
System.out.println(name+" say hello");
if (name !=null)
return name+" say hello";
return "通了 但是参数没有传递过来";
}
}
说明:实现类上需要贴上注解,方法不需要
四、自定义cxf拦截器
说在前头:这里我只是实验了拦截器的定义,没有什么具体的业务操作,想学习的同学请自行研究
package cn.lyn.webservice.interceptor;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
/**
* @author LengYouNuan
* @create 2021-05-24 下午3:05
*/
public class CxfInterceptor extends AbstractPhaseInterceptor{
private static final Logger log = LogManager.getLogger(CxfInterceptor.class);
public CxfInterceptor(String phase) {
super(phase);
}
public CxfInterceptor(){
super(Phase.PRE_PROTOCOL);
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
System.out.println("=======================================");
List headers = message.getHeaders();
log.info(headers);
}
}
五、配置cxf信息
package cn.lyn.webservice.config;
import cn.lyn.webservice.interceptor.CxfInterceptor;
import cn.lyn.webservice.serviceone.WebServiceInterface;
import cn.lyn.webservice.serviceone.impl.WebServiceImpl;
import org.apache.cxf.Bus;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
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.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author LengYouNuan
* @create 2021-05-24 下午2:19
*/
@Configuration
public class CxfConfig implements WebMvcConfigurer {
/**
* 此方法被注释后:wsdl访问地址为http://127.0.0.1:8080/services/user?wsdl
* 去掉注释后:wsdl访问地址为:http://127.0.0.1:8080/soap/user?wsdl
*/
@Bean
public ServletRegistrationBean creatDispatcherServlet() {
return new ServletRegistrationBean(new CXFServlet(), "/soap/*");
}
/**
* 非必要项
* 用于打印cxf日志信息
*/
@Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
SpringBus springBus = new SpringBus();
//将cxf拦截器添加到bus里
CxfInterceptor cxfInterceptor = new CxfInterceptor();
springBus.getInInterceptors().add(cxfInterceptor);
return springBus;
}
@Bean
public WebServiceInterface getWebSer() {
return new WebServiceImpl();
}
/**
* 发布endpoint
*/
@Bean
public Server endpoint() {
//EndpointImpl endpoint = new EndpointImpl(springBus(),getWebSer());
//发布地址
//endpoint.publish("/sayHello");
//return endpoint;
/**
* ===上边是java原生方式发布服务;下边是cxf方式发布服务 发布之后生成的wsdl文档信息有很明显的区别
*因为我是采用soap方式作为客户端 所以只能用cxf方式发布服务
* 如果客户端是采用http请求方式,则对发布方式没有严格要求
*
*/
//以下发布方式会在wsdl文档中生成对应方法和参数的标签信息
WebServiceImpl hw = new WebServiceImpl();
JaxWsServerFactoryBean jwsFactory = new JaxWsServerFactoryBean();
jwsFactory.setAddress("sayHello"); //指定WebService的发布地址
jwsFactory.setServiceClass(WebServiceInterface.class);//WebService对应的类型
jwsFactory.setServiceBean(hw);//WebService对应的实现对象
return jwsFactory.create();
}
@Bean
public CxfInterceptor getCxfInterceptor() {
return new CxfInterceptor();
}
}
此时启动boot项目就可以访问到wsdl文档
六、客户端编写
package cn.lyn.webservice.client;
import com.sun.xml.internal.ws.client.BindingProviderProperties;
import com.sun.xml.internal.ws.developer.JAXWSProperties;
import org.w3c.dom.Document;
import javax.xml.namespace.QName;
import javax.xml.soap.*;
import javax.xml.ws.Dispatch;
import javax.xml.ws.Service;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
/**
* soap方式调用webservice方式客户端
* @author LengYouNuan
* @create 2021-05-31 下午2:35
*/
public class SoapClient {
String nameSpace = ""; //wsdl的命名空间
String wsdlUrl = ""; //wsdl文档地址
String serviceName = ""; //服务的名字
String portName = "";
String responseName = ""; //@WebResult:注解上的name值
String elementName = ""; //默认是要访问的方法名 如果@WebMethod属性name有值 则是该值,实际还是以wsdl文档为主
int timeout = 20000;
/**
*
* @param nameSpace
* @param wsdlUrl
* @param serviceName
* @param portName
* @param element
* @param responseName
*/
public SoapClient(String nameSpace, String wsdlUrl,
String serviceName, String portName, String element,
String responseName) {
this.nameSpace = nameSpace;
this.wsdlUrl = wsdlUrl;
this.serviceName = serviceName;
this.portName = portName;
this.elementName = element;
this.responseName = responseName;
}
/**
*
* @param nameSpace
* @param wsdlUrl
* @param serviceName
* @param portName
* @param element
* @param responseName
* @param timeOut
* 毫秒
*/
public SoapClient(String nameSpace, String wsdlUrl,
String serviceName, String portName, String element,
String responseName, int timeOut) {
this.nameSpace = nameSpace;
this.wsdlUrl = wsdlUrl;
this.serviceName = serviceName;
this.portName = portName;
this.elementName = element;
this.responseName = responseName;
this.timeout = timeOut;
}
public String sendMessage(HashMap inMsg) throws Exception {
// 创建URL对象
URL url = null;
try {
url = new URL(wsdlUrl);
} catch (Exception e) {
e.printStackTrace();
return "创建URL对象异常";
}
// 创建服务(Service)
QName sname = new QName(nameSpace, serviceName);
Service service = Service.create(url, sname);
// 创建Dispatch对象
Dispatch dispatch = null;
try {
dispatch = service.createDispatch(new QName(nameSpace, portName),
SOAPMessage.class, Service.Mode.MESSAGE);
} catch (Exception e) {
e.printStackTrace();
return "创建Dispatch对象异常";
}
// 创建SOAPMessage
try {
SOAPMessage msg = MessageFactory.newInstance(
SOAPConstants.SOAP_1_1_PROTOCOL).createMessage();
msg.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, "UTF-8");
SOAPEnvelope envelope = msg.getSOAPPart().getEnvelope();
// 创建SOAPHeader(不是必需)
// SOAPHeader header = envelope.getHeader();
// if (header == null)
// header = envelope.addHeader();
// QName hname = new QName(nameSpace, "username", "nn");
// header.addHeaderElement(hname).setValue("huoyangege");
// 创建SOAPBody
SOAPBody body = envelope.getBody();
QName ename = new QName(nameSpace, elementName, "q0");
SOAPBodyElement ele = body.addBodyElement(ename);
// 增加Body元素和值
for (Map.Entry entry : inMsg.entrySet()) {
ele.addChildElement(new QName(nameSpace, entry.getKey()))
.setValue(entry.getValue());
}
// 超时设置
dispatch.getRequestContext().put(
BindingProviderProperties.CONNECT_TIMEOUT, timeout);
dispatch.getRequestContext().put(JAXWSProperties.REQUEST_TIMEOUT,
timeout);
// 通过Dispatch传递消息,会返回响应消息
SOAPMessage response = dispatch.invoke(msg);
// 响应消息处理,将响应的消息转换为doc对象
Document doc = response.getSOAPPart().getEnvelope().getBody()
.extractContentAsDocument();
String ret = doc.getElementsByTagName(responseName).item(0)
.getTextContent();
return ret;
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
public static void main(String[] args) throws Exception {
SoapClient soapClient=new SoapClient("http://serviceone.webservice.lyn.cn/","http://127.0.0" +
".1:8080/soap/sayHello?wsdl","WebServiceInterfaceService","WebServiceInterfacePort","sayHello",
"sayHelloResponse",
2000);
//封装请求参数
HashMap msg=new HashMap<>();
msg.put("name","啊哈");
String s = soapClient.sendMessage(msg);
System.out.println(s);
}
}
总结、其实我踩的最大的坑主要有两部分
第一个就是请求参数无法传递进去,解决办法是设置@WebParam注解的targetNamespace属性值
这个主要就是报非法属性异常
Unmarshalling Error: 意外的元素 (uri:"http://service
s.bingosoft.net/", local:"arg1")。所需元素为<{}arg5>,<{}arg4>,<{}arg3>,<{}arg2>,
<{}arg1>,<{}arg0>
第二个就是无法获取到返回值,解决办法就是需要添加@WebResult注解并设置对应的属性值
这个主要就是在客户端获取返回值哪里报空指针异常