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

简介

在多系统交互中,有时候需要以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配置示例如下:

XML 复制代码
<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>

生成代码如下:

调用方式:

java 复制代码
@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的值可以自动定义,这样在借助代码的形式就可以做到一键部署。

其实现代码如下:

java 复制代码
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>

代码实现如下:

java 复制代码
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依赖,实现无客户端访问,所需依赖如下:

XML 复制代码
<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>

代码实现如下:

java 复制代码
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配置文件

XML 复制代码
<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,对应的配置和以前不一样了:

java 复制代码
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的区别。

完整可运行代码如下:

java 复制代码
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的形式,考虑到二者实现其实各有优劣,有需要的读者可以自行甄别选用。

相关推荐
embrace999 小时前
【C语言学习】预处理详解
java·c语言·开发语言·数据结构·c++·学习·算法
山沐与山9 小时前
【Flink】Flink架构深度剖析:JobManager与TaskManager
java·架构·flink
Hello.Reader9 小时前
Flink SQL「SHOW / SHOW CREATE」元数据巡检、DDL 复刻与排障速查(含 Java 示例)
java·sql·flink
Doris_LMS9 小时前
接口、普通类和抽象类
java
重生之我是Java开发战士9 小时前
【数据结构】优先级队列(堆)
java·数据结构·算法
菜鸟233号9 小时前
力扣216 组合总和III java实现
java·数据结构·算法·leetcode
dodod20129 小时前
Ubuntu24.04.3执行sudo apt install yarnpkg 命令失败的原因
java·服务器·前端
Evan芙9 小时前
搭建 LNMT 架构并配置 Tomcat 日志管理与自动备份
java·架构·tomcat
青云交9 小时前
Java 大视界 -- Java+Spark 构建企业级用户画像平台:从数据采集到标签输出全流程(437)
java·开发语言·spark·hbase 优化·企业级用户画像·标签计算·高并发查询
铉铉这波能秀9 小时前
正则表达式从入门到精通(字符串模式匹配)
java·数据库·python·sql·正则表达式·模式匹配·表格处理