Java客户端调用SOAP方式的WebService服务实现方式分析

bigbearxyz 2024-07-30 09:33:01 阅读 72

简介

在多系统交互中,有时候需要以Java作为客户端来调用SOAP方式的WebService服务,本文通过分析不同的调用方式,以Demo的形式,帮助读者在生产实践中选择合适的调用方式。

本文JDK环境为JDK17。

结论

推荐使用Axis2或者Jaxws,以无客户端的形式来调用WebService。

有客户端,推荐Maven插件。

有客户端调用

主要时利用wsdl文档,自动生成对应的Java代码来实现

建议在pom文件中,配置对应的Maven插件来实现WebService客户端代码的自动生成。

JDK wsimport命令生成(不推荐)

简介

主要是利用jdk的自带工具wsimport工具实现,执行命令如下:

wsimport -s C:\tmp\com -p com.example.demo5.wsdl -encoding utf-8 http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx?wsdl

优点

通常装有JDK的电脑或者服务器都可以直接运行,方便生成。

缺点

在实际的运用中wsimport命令会有很多问题,首先只有JDK1.8才支持这个命令,即使能使用,仍然存在一些问题。其次,在JDK17以上没有自带这个工具,可能要安装插件才能使用,但是笔者安装了一些插件仍然无法使用。

ApacheCXF自动生成(不推荐)

简介

ApacheCXF通过安装也可以自动生成对应的WebService客户端代码。具体操作可见链接。

缺点

需要额外安装ApacheCXF插件。

Maven插件自动生成(推荐)

简介

通过spring.io网址的Demo示例,可以配置pom的maven插件,自动生成代码。

demo获取链接如下:

Getting Started | Consuming a SOAP web service (spring.io)

pom配置示例如下:

<code><build>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

</plugin>

<!-- tag::wsdl[] -->

<plugin>

<groupId>com.sun.xml.ws</groupId>

<artifactId>jaxws-maven-plugin</artifactId>

<version>3.0.0</version>

<executions>

<execution>

<goals>

<goal>wsimport</goal>

</goals>

</execution>

</executions>

<configuration>

<packageName>com.example.consumingwebservice.wsdl</packageName>

<wsdlUrls>

<!--<wsdlUrl>http://localhost:8080/ws/countries.wsdl</wsdlUrl>-->

<wsdlUrl>http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx?wsdl</wsdlUrl>

</wsdlUrls>

<sourceDestDir>${sourcesDir}</sourceDestDir>

<destDir>${classesDir}</destDir>

<extension>true</extension>

</configuration>

</plugin>

<!-- end::wsdl[] -->

</plugins>

</build>

生成代码如下:

调用方式:

<code>@RequestMapping(value = "/{ip}", method = RequestMethod.GET)

public ArrayOfString searchIp(@PathVariable("ip") String ip) {

IpAddressSearchWebServiceSoap ipAddressSearchWebServiceSoap = new IpAddressSearchWebService().getIpAddressSearchWebServiceSoap();

ArrayOfString response = ipAddressSearchWebServiceSoap.getCountryCityByIp(ip);

return response;

}

优点

操作简单,改动小。

缺点

唯一的缺点,也是有客户端调用普遍存在的,自动生成代码后,需要重新部署一次。

Springboot集成Git插件实现

通过Springboot集成git插件,可以通过接口的形式来修改maven的wsdlUrls配置,然后推送到git服务,最后触发Jenkins自动部署。

以Git推送代码的形式来实现代码的自动生成,其缺点是,每次根据一份wsdl文件生成完代码,需要重启一次服务,但是笔者通过自动配置的形式可以做到一键部署。

其中触发Jenkins自动部署,可以通过git的配置实现,通过访问特定的url实现。配置好的git部署链接如下:

http://192.168.22.22:8080/job/demo_test/build?token=1987654567890hjkoijghfvgjjnmkjkmk

其中token的值可以自动定义,这样在借助代码的形式就可以做到一键部署。

其实现代码如下:

<code>package com.example.consumingwebservice;

import java.io.File;

import java.io.IOException;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.nio.file.StandardOpenOption;

import java.util.Date;

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.eclipse.jgit.api.Git;

import org.eclipse.jgit.lib.Repository;

import org.eclipse.jgit.transport.CredentialsProvider;

import org.eclipse.jgit.transport.HttpConfig;

import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;

public class GitUtil {

//private static Log log = LogFactory.getLog(GitUtil.class);

private GitUtil() {

}

public static Git getGit(String uri, CredentialsProvider credentialsProvider, String localDir) throws Exception {

Git git = null;

if (new File(localDir).exists() ) {

git = Git.open(new File(localDir));

} else {

git = Git.cloneRepository().setCredentialsProvider(credentialsProvider).setURI(uri)

.setDirectory(new File(localDir)).call();

}

//设置一下post内存,否则可能会报错Error writing request body to server

git.getRepository().getConfig().setInt(HttpConfig.HTTP, null, HttpConfig.POST_BUFFER_KEY, 512*1024*1024);

return git;

}

public static CredentialsProvider getCredentialsProvider(String username, String password) {

return new UsernamePasswordCredentialsProvider(username, password);

}

public static Repository getRepository(Git git) {

return git.getRepository();

}

public static void pull(Git git, CredentialsProvider credentialsProvider) throws Exception {

git.pull().setRemote("origin").setCredentialsProvider(credentialsProvider).call();

}

public static void push(Git git, CredentialsProvider credentialsProvider, String filepattern, String message)

throws Exception {

git.add().addFilepattern(filepattern).call();

git.add().setUpdate(true);

git.commit().setMessage(message).call();

git.push().setCredentialsProvider(credentialsProvider).call();

}

public static void main(String[] args) throws Exception {

String uri = "http://192.168.9.11/test/webservice.git";

String username = "343535@qq.com";

String password = "xdfetrfrr";

CredentialsProvider credentialsProvider = getCredentialsProvider(username, password);

String localDir = "C:/tmp/git_test";

Git git = getGit(uri, credentialsProvider, localDir);

pull(git, credentialsProvider);

changeFile(localDir + "/pom.xml");

// push(git, credentialsProvider, ".", "提交文件");

push(git, credentialsProvider, "pom.xml", "修改pom文件" + new Date());

}

private static final String newText = " <wsdlUrl>http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx?wsdl</wsdlUrl>\r\n </wsdlUrls>";

protected static void changeFile(String filePath) {

try {

// 读取文本文件的内容

Path path = Paths.get(filePath);

String content = Files.readString(path);

System.out.println(content);

// 替换内容

String modifiedContent = content.replace("</wsdlUrls>", newText);

// 将修改后的内容写回文本文件

Files.write(path, modifiedContent.getBytes(), StandardOpenOption.WRITE);

System.out.println("文本文件内容已成功修改!");

} catch (IOException e) {

System.out.println("修改文本文件内容时出现错误:" + e.getMessage());

}

}

}

 如要实现流程图的规划,可以后台通过http的get请求上文的git部署链接,实现接口的自动部署。

无客户端调用

也就是不需要按wsdl的格式来生成对应的Java代码,原理时通过构建xml的形式来访问WebService。

这里推荐使用Axis2或者Jaxws的方式来调用,二者各有优劣。

Axis调用(不推荐)

简介

通过pom引入axis依赖,实现无客户端访问,所需依赖如下:

<dependency>

<groupId>axis</groupId>

<artifactId>axis</artifactId>

<version>1.4</version>

</dependency>

代码实现如下:

public static void main(String[] args){

try {

String nameSpac = "http://WebXml.com.cn/";

URL url = new URL("http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx?wsdl");

QName sname = new QName(nameSpac, "MobileCodeWS");

QName pname = new QName(nameSpac, "MobileCodeWSSoap");

Service service = new Service( url, sname);

Call call = (Call)service.createCall(pname);

call.setSOAPActionURI(nameSpac + "getMobileCodeInfo");

call.setOperationName(new QName(nameSpac, "getMobileCodeInfo")); // 需要请求的方法

call.addParameter(new QName(nameSpac, "mobileCode"), XMLType.XSD_STRING, ParameterMode.IN); // 入参

call.addParameter(new QName(nameSpac, "userID"), XMLType.XSD_STRING, ParameterMode.IN); // 入参

// call.addParameter("param3", XMLType.SOAP_STRING, ParameterMode.IN); // 入参

String param1 = "15932582632"; // 参数

String param2 = null; // 参数

call.setReturnClass(String.class); // 设置返回值

call.setUseSOAPAction(true);

Object invoke = call.invoke(new Object[]{param1, param2});// 调用获取返回值

// Object invoke = call.invoke(new Object[]{});// 调用获取返回值

System.out.println(invoke);

}catch (Exception e){

e.printStackTrace();

}

}

优点

较少的代码量,依赖需要少,实现简单

缺点

通过笔者的实验,发现Axis的调用并不稳定,对于不同的接口,有的接口无参数调用可以调通,有参数调用会报错,有的接口有参数调用可以调通(如例),无参数调用会报错。

实际上,这个依赖在2006年便没有维护了,它的功能转移到了Axis2。

Axis2调用(推荐)

简介

通过pom引入axis2依赖,实现无客户端访问,所需依赖如下:

<dependency>

<groupId>org.apache.axis2</groupId>

<artifactId>axis2-jaxws</artifactId>

<version>1.7.0</version>

</dependency>

<dependency>

<groupId>org.apache.axis2</groupId>

<artifactId>axis2-adb-codegen</artifactId>

<version>1.7.0</version>

</dependency>

<dependency>

<groupId>org.apache.axis2</groupId>

<artifactId>axis2-transport-local</artifactId>

<version>1.7.0</version>

</dependency>

<dependency>

<groupId>org.apache.axiom</groupId>

<artifactId>com.springsource.org.apache.axiom</artifactId>

<version>1.2.5</version>

</dependency>

代码实现如下:

import org.apache.axiom.om.OMAbstractFactory;

import org.apache.axiom.om.OMElement;

import org.apache.axiom.om.OMFactory;

import org.apache.axiom.om.OMNamespace;

import org.apache.axis2.AxisFault;

import org.apache.axis2.addressing.EndpointReference;

import org.apache.axis2.client.Options;

import org.apache.axis2.client.ServiceClient;

import org.apache.axis2.transport.http.impl.httpclient3.HttpTransportPropertiesImpl;

/**

*

* @ClassName: MobileClientDoc

* @Description: TODO

* 方法二: 应用document方式调用 用ducument方式应用现对繁琐而灵活。现在用的比较多。因为真正摆脱了我们不想要的耦合

* 即使用org.apache.axis2.client.ServiceClient类进行远程调用web服务,不生成客户端

*

* @date 2017年11月9日 下午1:27:17

*

*/

public class SoapAxis2Client {

private static String requestName = "getCountryCityByIp";

public static void ipWS() {

try {

ServiceClient serviceClient = new ServiceClient();

//创建服务地址WebService的URL,注意不是WSDL的URL

String url = "http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx";

EndpointReference targetEPR = new EndpointReference(url);

Options options = serviceClient.getOptions();

options.setTo(targetEPR);

//确定调用方法(wsdl 命名空间地址 (wsdl文档中的targetNamespace) 和 方法名称 的组合)

options.setAction("http://WebXml.com.cn/" + requestName);

//设置密码

HttpTransportPropertiesImpl.Authenticator auth = new HttpTransportPropertiesImpl.Authenticator();

// auth.setUsername(username); //服务器访问用户名

// auth.setPassword(password); //服务器访问密码

// options.setProperty(HTTPConstants.AUTHENTICATE, auth);

OMFactory fac = OMAbstractFactory.getOMFactory();

/*

* 指定命名空间,参数:

* uri--即为wsdl文档的targetNamespace,命名空间

* perfix--可不填

*/

OMNamespace omNs = fac.createOMNamespace("http://WebXml.com.cn/", "");

// 指定方法

OMElement method = fac.createOMElement(requestName, omNs);

// 指定方法的参数

OMElement theIpAddress = fac.createOMElement("theIpAddress", omNs);

theIpAddress.setText("111.249.198.56");

// OMElement userID = fac.createOMElement("userID", omNs);

// userID.setText("");

method.addChild(theIpAddress);

// method.addChild(userID);

method.build();

//远程调用web服务

OMElement result = serviceClient.sendReceive(method);

//值得注意的是,返回结果就是一段由OMElement对象封装的xml字符串。

String xml = result.cloneOMElement().toString();

System.out.println(xml);

} catch (AxisFault axisFault) {

axisFault.printStackTrace();

}

}

public static void main(String[] args) throws AxisFault {

ipWS();

}

}

优点

代码量较少,通过配置xml节点实现系统调用,可以设置灵活的调用方式。经过实验,对各种WebService接口的有参无参调用,都能取得正确的返回结果。

测试结果如下:

<getCountryCityByIpResponse xmlns="http://WebXml.com.cn/"><getCountryCityByIpResult><string>111.249.198.56</string><string>台湾省  </string></getCountryCityByIpResult></getCountryCityByIpResponse>

缺点

所需的pom配置文件较多,且引用不正确较难排查问题,且各个pom之间的版本冲突也需要解决。

Jaxws调用(推荐)

简介

引入对于的pom配置文件

<code><dependency>

<groupId>org.apache.axis2</groupId>

<artifactId>axis2-jaxws</artifactId>

<version>1.7.0</version>

</dependency>

这里提前说下,下面代码大部分来自于csdn作者——LengYouNuan的文章,但是实在找不到对于作者了,提前声明。

还有它的原始代码并不能正常运行,会有服务器未能识别 HTTP 头 SOAPAction 的值的报错,笔者通过实验和研究,添加了如下配置,才能正常运行:

//这句话很重要,否则报错服务器未能识别 HTTP 头 SOAPAction 的值

dispatch.getRequestContext().put(SOAPACTION_URI_PROPERTY, nameSpace + elementName);

dispatch.getRequestContext().put(SOAPACTION_USE_PROPERTY, true);

由于使用的JDK17,对应的配置和以前不一样了:

public interface BindingProvider {

String USERNAME_PROPERTY = "jakarta.xml.ws.security.auth.username";

String PASSWORD_PROPERTY = "jakarta.xml.ws.security.auth.password";

String ENDPOINT_ADDRESS_PROPERTY = "jakarta.xml.ws.service.endpoint.address";

String SESSION_MAINTAIN_PROPERTY = "jakarta.xml.ws.session.maintain";

String SOAPACTION_USE_PROPERTY = "jakarta.xml.ws.soap.http.soapaction.use";

String SOAPACTION_URI_PROPERTY = "jakarta.xml.ws.soap.http.soapaction.uri";

......

应该主要是javax和jakarta的区别。

完整可运行代码如下:

package com.example.consumingwebservice;

import com.sun.xml.ws.client.BindingProviderProperties;

import com.sun.xml.ws.developer.JAXWSProperties;

import jakarta.xml.soap.*;

import jakarta.xml.ws.Dispatch;

import jakarta.xml.ws.Service;

import org.w3c.dom.Document;

import javax.xml.namespace.QName;

import java.net.URL;

import java.util.HashMap;

import java.util.Map;

import static jakarta.xml.ws.BindingProvider.SOAPACTION_URI_PROPERTY;

import static jakarta.xml.ws.BindingProvider.SOAPACTION_USE_PROPERTY;

/**

* soap方式调用webservice方式客户端

*

* @author LengYouNuan

* @create 2021-05-31 下午2:35

*/

public class SoapJaxwsClient {

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 SoapJaxwsClient(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 SoapJaxwsClient(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<String, String> 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<SOAPMessage> dispatch = null;

try {

dispatch = service.createDispatch(new QName(nameSpace, portName), SOAPMessage.class, Service.Mode.MESSAGE);

} catch (Exception e) {

e.printStackTrace();

return "创建Dispatch对象异常";

}

// 创建SOAPMessage

try {

//这句话很重要,否则报错服务器未能识别 HTTP 头 SOAPAction 的值

dispatch.getRequestContext().put(SOAPACTION_URI_PROPERTY, nameSpace + elementName);

dispatch.getRequestContext().put(SOAPACTION_USE_PROPERTY, true);

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, "");

SOAPBodyElement ele = body.addBodyElement(ename);

// 增加Body元素和值

for (Map.Entry<String, String> 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://spring.io/guides/gs-producing-web-service","http://localhost:8080/ws/countries.wsdl",

// "CountriesPortService","CountriesPortSoap11","getCountry",

// "getCountryResponse",

// 2000);

SoapJaxwsClient soapClient = new SoapJaxwsClient("http://WebXml.com.cn/", "http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx?wsdl",

"MobileCodeWS", "MobileCodeWSSoap", "getDatabaseInfo",

"getDatabaseInfoResponse",

2000);

// SoapClient soapClient=new SoapClient("http://WebXml.com.cn/","http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.wsdl",

// "IpAddressSearchWebService","IpAddressSearchWebServiceSoap","getCountryCityByIp",

// "getCountryCityByIpResult",

// 2000);

//封装请求参数

HashMap<String, String> msg = new HashMap<>();

// msg.put("theIpAddress","111.249.198.56");

// msg.put("mobileCode","18702750020");

// msg.put("userID","");

String s = soapClient.sendMessage(msg);

System.out.println(s);

}

}

测试结果:

优点

pom配置简单,无须解决各种版本依赖的问题。

缺点

可以看到Jaxws的调用和Axis2一样,都具有较高的灵活性,都可以自定义xml的节点数据。

所不同的是,它的调用代码稍显繁琐,但如果在生产中,有良好的封装,这应该不是问题。

小结

对于SOAP方式WebService的调用,有客户端的调用,推荐maven插件自动生成代码的形式,唯一的缺点是需要重新部署一次。

对于无客户端的调用,推荐Axis2或者Jaxws的形式,考虑到二者实现其实各有优劣,有需要的读者可以自行甄别选用。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。