简介
在多系统交互中,有时间需要以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配置示比方下:
- <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>
复制代码 生成代码如下:

调用方式:
- @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的值可以自动定义,这样在借助代码的情势就可以做到一键部署。

实在当代码如下:
- 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配置文件
- <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的情势,考虑到二者实现实在各有优劣,有需要的读者可以自行甄别选用。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |