前不久,为客户开发的一个身份验证功能开发要用到Webservice,我是第一次接触Webservice的开发。
刚开始用CXF的方式去调用Webservice,成功了,但是部署到测试环境报错了,原因是CXF依赖与JDK的一个tool.jar,而测试环境使用的是JRE环境(JRE环境没有tool.jar这个jar包),因而CXF调用Webservice的方式行不通。于是,我又寻找了其它调用Webservice的方式进行尝试,AXIS、AXIS2,HttpClient,这三种方式都试过了,也踩了不少坑,最后使用HttpClient的方式解决了问题。
在此,我需要将这个踩坑过程记录,加深Webservice调用方式的知识点。
这次Webservice身份验证功能接口我一共使用了四种方式去调用,它们分别是:
(1)CXF方式。
(2)AXIS方式。
(3)AXIS2方式
(4)Http Client方式。
下面,对这四种调用方式进行介绍。可能某些方面介绍的不太齐全,但希望能大家能有所帮助。
首先看一下Webservice服务端的代码,这是一个非常简单的Webservice接口,方便理解Webservice调用的交互流程。
package com.chenlw.webservice;
import javax.jws.WebService;
import javax.xml.ws.Endpoint;
/**
* Title: ServiceHello
* Description: 基于jdk1.6以上的javax.jws 发布webservice接口
*
* @author panchengming
* @WebService - 它是一个注解,用在类上指定将此类发布成一个ws。
* Endpoint – 此类为端点服务类,它的方法publish用于将一个已经添加了@WebService注解
* 对象绑定到一个地址的端口上。
* Version:1.0.0
*/
@WebService
public class MyWebServiceTester {
public static String JWS_SERVER_URL = "http://192.168.174.1:9090/MyWebService/testWebService";
public static void main(String[] args) {
// 通过EndPoint(端点服务)发布一个WebService
// 发布成功后 在浏览器输入 http://192.168.174.1:9090/MyWebService/testWebService?wsdl
Endpoint.publish(JWS_SERVER_URL, new MyWebServiceTester());
System.out.println("发布成功!");
}
/**
* 供客户端调用方法 该方法是非静态的,会被发布
*
* @param param 传入参数
* @return String 返回结果
*/
public String testWebService(String param) {
return "Webservice接口返回数据:" + param;
}
}
代码运行后,在浏览器输入http://192.168.174.1:9090/MyWebService/testWebService?wsdl可以如下内容(这是WSDL,即基于XML语言的Web服务描述语言)。
Apache CXF 是一个开源的 Services 框架,CXF 帮助您利用 Frontend 编程 API 来构建和开发 Services ,像 JAX-WS 。这些 Services 可以支持多种协议,比如:SOAP、XML/HTTP、RESTful HTTP 或者 CORBA ,并且可以在多种传输协议上运行,比如:HTTP、JMS 或者 JBI,CXF 大大简化了 Services 的创建,同时它继承了 XFire 传统,一样可以天然地和 Spring 进行无缝集成。【来源于:百度百科CXF】
测试代码:
package com.chenlw.webservice;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
/**
* @author chenlw 2019/10/02
*/
public class CxfClientTester {
public static final String SERVICE_URL = "http://192.168.174.1:9090/MyWebService/testWebService?wsdl";
public static final String METHOD_NAME = "testWebService";
public static void main(String[] args) {
try {
test1();
} catch (Exception e) {
System.out.println("ERROR:" + e.getMessage());
}
}
public static void test1() throws Exception {
JaxWsDynamicClientFactory clientFactory = JaxWsDynamicClientFactory.newInstance();
Client client = clientFactory.createClient(SERVICE_URL);
Object[] result = client.invoke(METHOD_NAME, "hello");
System.out.println(result[0]);
}
}
运行结果:
如果调用webservice接口需要添加身份验证参数怎么办呢?请看1.3章节,在HTTP请求头可以添加身份验证的参数。
测试代码:
package com.chenlw.webservice;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* @author chenlw 2019/10/02
*/
public class CxfClientTester {
public static final String SERVICE_URL = "http://192.168.174.1:9090/MyWebService/testWebService?wsdl";
public static final String METHOD_NAME = "testWebService";
public static void main(String[] args) {
try {
// test1();
test2();
} catch (Exception e) {
System.out.println("ERROR:" + e.getMessage());
}
}
/**
* 调用Webservice
*
* @throws Exception 异常
*/
public static void test1() throws Exception {
JaxWsDynamicClientFactory clientFactory = JaxWsDynamicClientFactory.newInstance();
Client client = clientFactory.createClient(SERVICE_URL);
Object[] result = client.invoke(METHOD_NAME, "hello");
System.out.println(result[0]);
}
/**
* 调用Webservice
* 添加HTTP请求头
*
* @throws Exception
*/
public static void test2() throws Exception {
JaxWsDynamicClientFactory clientFactory = JaxWsDynamicClientFactory.newInstance();
Client client = clientFactory.createClient(SERVICE_URL);
// 添加HTTP请求头
HttpHeaderInterceptor httpHeaderInterceptor = new HttpHeaderInterceptor();
httpHeaderInterceptor.setTicket("ticket");
client.getOutInterceptors().add(httpHeaderInterceptor);
Object[] result = client.invoke(METHOD_NAME, "hello");
System.out.println(result[0]);
}
public static class HttpHeaderInterceptor extends AbstractPhaseInterceptor {
private String ticket;
public HttpHeaderInterceptor() {
super(Phase.POST_PROTOCOL);
}
@Override
public void handleMessage(Message message) throws Fault {
// 添加HTTP请求头
Map headers = (Map) message.get(Message.PROTOCOL_HEADERS);
try {
System.out.println("ticket: " + ticket);
headers.put("ticket", Collections.singletonList(ticket));
} catch (Exception ce) {
throw new Fault(ce);
}
}
public String getTicket() {
return ticket;
}
public void setTicket(String ticket) {
this.ticket = ticket;
}
}
}
运行结果:
axis全称Apache Extensible Interaction System 即阿帕奇可扩展交互系统。Axis本质上就是一个SOAP引擎,提供创建服务器端、客户端和网关SOAP操作的基本框架。【来源于:百度百科】
测试代码:
package com.chenlw.webservice.axis;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;
import org.apache.axis.encoding.XMLType;
import javax.xml.namespace.QName;
import javax.xml.rpc.ParameterMode;
/**
* @author chenlw
* @date 2019/10/02
*/
public class AxisClientTester2 {
public static final String SERVICE_URL = "http://192.168.174.1:9090/MyWebService/testWebService?wsdl";
public static final String TARGET_NAMESPACE = "http://webservice.chenlw.com/";
public static final String METHOD_NAME = "testWebService";
public static void main(String[] args) {
try {
testAxis1();
} catch (Exception e) {
System.out.println("错误:" + e.getMessage());
}
}
/**
* 通过axis方式调用webservice接口
*/
public static void testAxis1() {
try {
// 创建一个服务(service)调用(call)
Service service = new Service();
// 通过service创建call对象
Call call = (Call) service.createCall();
// 设置service所在URL
call.setTargetEndpointAddress(new java.net.URL(SERVICE_URL));
// 设置方法名,需要设定TARGET_NAMESPACE,否则可能会报错的。
// call.setOperationName(METHOD_NAME);
call.setOperationName(new QName(TARGET_NAMESPACE, METHOD_NAME));
// call.setUseSOAPAction(true);
// 变量最好只是用String类型,其他类型会报错
// 入参,在设定参数时,不使用服务端定义的参数名,而是arg0~argN来定义,也不需制定namespaceURI
call.addParameter("arg0", XMLType.SOAP_STRING, ParameterMode.IN);
// 设置参数名 state 第二个参数表示String类型,第三个参数表示入参
// call.addParameter("param", org.apache.axis.encoding.XMLType.XSD_STRING, javax.xml.rpc.ParameterMode.IN);
// 设置返回类型
call.setReturnType(org.apache.axis.encoding.XMLType.XSD_STRING);
// 此处为数组,有几个变量传几个变量
Object jsonString = call.invoke(new Object[]{"param"});
// 输出SOAP请求报文
System.out.println("--SOAP Request: " + call.getMessageContext().getRequestMessage().getSOAPPartAsString());
// 输出SOAP返回报文
System.out.println("--SOAP Response: " + call.getResponseMessage().getSOAPPartAsString());
// 输出返回信息
System.out.println("result===" + jsonString.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
问题记录:
添加请求参数如果使用call.addParameter("param", org.apache.axis.encoding.XMLType.XSD_STRING, javax.xml.rpc.ParameterMode.IN);
返回结果表明服务器端接收到的参数为null。
解决办法:
在设定参数时,不使用服务端定义的参数名,而是arg0~argN来定义,也不需制定namespaceURI
。就是这句代码:call.addParameter("arg0", XMLType.SOAP_STRING, ParameterMode.IN);
Axis2是下一代 Apache Axis。Axis2 虽然由 Axis 1.x 处理程序模型提供支持,但它具有更强的灵活性并可扩展到新的体系结构。Axis2 基于新的体系结构进行了全新编写,而且没有采用 Axis 1.x 的常用代码。支持开发 Axis2 的动力是探寻模块化更强、灵活性更高和更有效的体系结构,这种体系结构可以很容易地插入到其他相关 Web 服务标准和协议(如 WS-Security、WS-ReliableMessaging 等)的实现中。
Apache Axis2 是Axis的后续版本,是新一代的SOAP引擎。【来源于:百度百科】
测试代码:
package com.chenlw.webservice.axis2;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.context.NamedValue;
import org.apache.axis2.rpc.client.RPCServiceClient;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.params.HttpMethodParams;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.List;
/**
* AXIS2调用Webservice
*
* @author chenlw
* @date 2019/10/02
*/
public class Axis2Tester1 {
public static final String targetEndpoint = "http://192.168.174.1:9090/MyWebService/testWebService?wsdl";
public static final String targetNamespace = "http://webservice.chenlw.com/";
public static final String METHOD = "testWebService";
public static void main(String[] args) {
try {
test1();
} catch (Exception e) {
System.out.println("异常:" + e.getMessage());
}
}
public static void test1() throws Exception {
//RPCServiceClient是RPC方式调用
RPCServiceClient client = new RPCServiceClient();
Options options = client.getOptions();
//设置调用WebService的URL
EndpointReference epf = new EndpointReference(targetEndpoint);
options.setTo(epf);
// 添加HTTP请求头
List headerList = new ArrayList<>();
headerList.add(new NamedValue("ticket", "ticket"));
options.setProperty(HTTPConstants.HTTP_HEADERS, headerList);
// 设置超时
options.setProperty(HTTPConstants.CONNECTION_TIMEOUT, 10 * 1000);
// 取消重复请求
options.setProperty(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(0, false));
QName qname = new QName(targetNamespace, METHOD);
//指定调用的方法和传递参数数据,及设置返回值的类型
Object[] result = client.invokeBlocking(qname, new Object[]{"param"}, new Class[]{String.class});
System.out.println(result[0]);
}
}
运行结果:
Http Client方式调用Webservice,其实是根据Webservice的交互流程来实现的。关键点在于把SOAP请求作为HTTP请求的正文,并且增加HTTP请求头,然后获取HTTP响应正文,解析结果。
测试代码中使用了org.apache.httpcomponents的jar包。
测试代码:
package com.chenlw.webservice.httpclient;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import java.nio.charset.StandardCharsets;
/**
* @author chenlw
* @date 2019/09/27
*/
public class WebserviceHttpClientTester2 {
public static final String SERVICE_URL = "http://192.168.174.1:9090/MyWebService/testWebService?wsdl";
/**
* Webservice连接超时时间
*/
public static final int CLIENT_CONNECT_TIMEOUT = 10 * 1000;
/**
* 直接用soapui中请求的数据
*/
public static final String requestXml = "\n" +
" \n" +
" \n" +
" \n" +
" \n" +
" %s \n" +
" \n" +
" \n" +
" ";
public static void main(String[] args) {
try {
testHttpClient();
} catch (Exception e) {
System.out.println("错误:" + e.getMessage());
}
}
public static void testHttpClient() throws Exception {
String param = "param";
String ticket = "ticket";
String requestData = String.format(requestXml, param);
String soapRes = callWebserviceUsingHttpClient(SERVICE_URL, requestData, ticket);
System.out.println("result:" + soapRes);
}
/**
* Http Client 调用Webservice接口
*
* @param wsdl Webservice接口
* @param requestData 请求数据
* @param ticket ticket
* @return 结果
*/
public static String callWebserviceUsingHttpClient(String wsdl, String requestData, String ticket) throws Exception {
String soapResponse = null;
HttpClientBuilder builder = HttpClientBuilder.create();
CloseableHttpClient client = builder.build();
try {
HttpPost httpPost = new HttpPost(wsdl);
// 添加HTTP请求头
httpPost.setHeader("ticket", ticket);
// 设置连接参数
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(CLIENT_CONNECT_TIMEOUT)
.setConnectionRequestTimeout(CLIENT_CONNECT_TIMEOUT)
.setSocketTimeout(CLIENT_CONNECT_TIMEOUT)
.build();
httpPost.setConfig(requestConfig);
// 请求正文
StringEntity stringEntity = new StringEntity(requestData, StandardCharsets.UTF_8.displayName());
//stringEntity.setContentType(ContentType.APPLICATION_XML.toString());
stringEntity.setContentType("text/xml; charset=utf-8");
httpPost.setEntity(stringEntity);
// 发送请求
CloseableHttpResponse response = client.execute(httpPost);
int responseStatusCode = response.getStatusLine().getStatusCode();
if (responseStatusCode != HttpStatus.SC_OK) {
System.out.println("Webservice接口未能正确处理请求,HTTP响应状态码为:" + responseStatusCode);
}
HttpEntity entity = response.getEntity();
if (entity != null) {
soapResponse = EntityUtils.toString(entity, StandardCharsets.UTF_8.displayName());
}
if (response != null) {
response.close();
}
} catch (Exception e) {
System.out.println("调用Webservice出错:" + e.getMessage());
throw e;
} finally {
if (client != null) {
client.close();
}
}
return soapResponse;
}
}
运行结果:
优点:API使用比较方便,不用写很多代码。
缺点:依赖于JDK的tool.jar包,如果运行环境是JRE则无法使用。
优点:API使用比较方便,不用写很多代码。
缺点:添加HTTP请求头比较麻烦;配置项多。
优点:API使用比较方便,不用写很多代码;配置项方便;
缺点:依赖JAR包多,有可能跟Web项目的jar包有冲突。
优点:接近于一种“原生代码”的方式调用Webservice,无需依赖过多的jar包,灵活性和扩展性较高,效率比框架实现快一些。
缺点:需要事先知道SOAP请求报文的格式(可用soapUI工具获取)。
以上就是我在项目实践过程中总结出来的优缺点,可能不够全面。仁者见仁智者见智吧。个人推荐调用Webservice的方式顺序如下:
(1)Http Client模式
(2)CXF方式
(3)AXIS2方式
(4)AXIS方式