杰瑞科技汇

Java如何调用C语言开发的WebService?

直接使用标准的 Java 客户端工具(如 wsimport)可能会遇到问题,尤其是在处理 .NET 特有的安全策略(如 WS-Security)时。

Java如何调用C语言开发的WebService?-图1
(图片来源网络,侵删)

下面我将为你详细讲解几种主流的方法,从最推荐到备选方案,并提供代码示例。


核心挑战

在开始之前,你需要了解 Java 调用 .NET WebService 主要面临的几个挑战:

  1. WS-Security (特别是 UsernameToken 和 Timestamp):这是最常见的问题。.NET WebService 经常要求客户端提供用户名和密码进行身份验证,并且消息中需要包含时间戳以防止重放攻击,标准的 wsimport 生成的代码可能无法直接支持这种复杂的策略。
  2. 消息格式和编码:虽然 SOAP 消息标准是统一的,但 .NET 和 Java 在处理默认的命名空间、SOAPAction 头部、字符集等方面可能存在细微差别。
  3. WSDL 文件:.NET 的 WSDL 文件有时会包含 Java 客户端工具不支持的特性或引用了 .NET 特有的扩展。

使用 Apache CXF (强烈推荐)

Apache CXF 是一个功能强大、灵活的开源 WebService 框架,它对 WS-Security 等标准有非常好的支持,并且提供了比标准 wsimport 更多的配置选项,是处理跨平台 WebService 调用的首选。

步骤 1:准备环境

确保你的项目中包含了 CXF 的核心依赖,如果你使用 Maven,可以添加以下依赖:

Java如何调用C语言开发的WebService?-图2
(图片来源网络,侵删)
<dependencies>
    <!-- CXF 核心依赖 -->
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-core</artifactId>
        <version>3.5.5</version> <!-- 使用最新稳定版 -->
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-frontend-jaxws</artifactId>
        <version>3.5.5</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-http</artifactId>
        <version>3.5.5</version>
    </dependency>
    <!-- WS-Security 依赖 -->
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-ws-security</artifactId>
        <version>3.5.5</version>
    </dependency>
    <!-- 如果需要日志来调试 SOAP 消息,可以添加这个 -->
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-ws-policy</artifactId>
        <version>3.5.5</version>
    </dependency>
</dependencies>

步骤 2:生成客户端代码

CXF 提供了 wsdl2java 工具,功能和 wsimport 类似,但通常更健壮。

# 在命令行中执行
wsdl2java -p com.example.client -d src/main/java http://your-csharp-webservice-url/YourService.asmx?wsdl
  • -p: 指定生成的 Java 包名。
  • -d: 指定代码输出的目录。

执行后,你会得到一系列 Java 文件,包括服务接口、实现类、数据模型等。

步骤 3:配置 WS-Security 并调用服务

这是最关键的一步,你需要创建一个 CXF 的 Bus,配置安全策略,然后创建服务代理。

假设你的 .NET 服务需要 UsernameTokenTimestamp

Java如何调用C语言开发的WebService?-图3
(图片来源网络,侵删)
package com.example.client;
import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.bus.spring.SpringBus;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.rt.security.saml.SamlCallbackHandler;
import org.apache.cxf.ws.security.SecurityConstants;
import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.apache.wss4j.common.ConfigurationConstants;
import org.apache.wss4j.common.crypto.Crypto;
import org.apache.wss4j.common.crypto.Merlin;
import org.apache.wss4j.dom.handler.WSHandlerConstants;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
public class CSharpWebServiceClient {
    public static void main(String[] args) {
        // 1. 创建 Bus (可选,但推荐)
        Bus bus = new SpringBus();
        BusFactory.setDefaultBus(bus);
        // 2. 创建服务客户端
        YourService_Service service = new YourService_Service();
        YourService port = service.getYourServiceSoap(); // 根据生成的代码调整
        // 3. 获取 CXF 客户端对象以进行配置
        Client client = ClientProxy.getClient(port);
        // 4. 配置出站安全拦截器 (Outbound Interceptor)
        // 这是发送 SOAP 请求时的安全策略
        Map<String, Object> outProps = new HashMap<>();
        // 用户名和密码
        outProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN + " " + WSHandlerConstants.TIMESTAMP);
        outProps.put(WSHandlerConstants.USER, "your-username");
        outProps.put(WSHandlerConstants.PASSWORD, "your-password");
        outProps.put(WSHandlerConstants.PASSWORD_TYPE, "PasswordText"); // 或 "PasswordDigest"
        // 时间戳
        outProps.put(WSHandlerConstants.TIMESTAMP_STRICT, "false"); // 或 "true" 根据服务要求
        outProps.put(WSHandlerConstants.TIMEESTAMP_TTL_SECONDS, "300"); // 时间戳有效时间
        client.getOutInterceptors().add(new WSS4JOutInterceptor(outProps));
        // 5. (可选) 配置入站安全拦截器 (Inbound Interceptor)
        // 用于处理服务端返回的 SOAP 消息中的安全头
        Map<String, Object> inProps = new HashMap<>();
        inProps.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
        client.getInInterceptors().add(new WSS4JInInterceptor(inProps));
        // 6. 调用 WebService 方法
        try {
            // 假设服务有一个名为 GetUserInfo 的方法,接受一个字符串参数
            String request = "test-user-id";
            String response = port.GetUserInfo(request);
            System.out.println("调用成功!");
            System.out.println("返回结果: " + response);
        } catch (Exception e) {
            System.err.println("调用失败!");
            e.printStackTrace();
        } finally {
            // 7. 清理 Bus
            BusFactory.setDefaultBus(null);
            bus.shutdown(true);
        }
    }
}

代码解释:

  • WSS4JOutInterceptor: 负责在发送 SOAP 请求前,根据 outProps 中的配置添加安全头(如 UsernameTokenTimestamp)。
  • WSHandlerConstants.ACTION: 定义了要执行的安全操作,这里是 USERNAME_TOKENTIMESTAMP 的组合。
  • WSHandlerConstants.USER / PASSWORD: 提供身份验证信息。
  • WSHandlerConstants.PASSWORD_TYPE: 指定密码是明文 (PasswordText) 还是摘要 (PasswordDigest),需要与 .NET 服务端配置一致。

使用 Spring-WS + WSS4J

如果你的项目已经广泛使用 Spring 框架,那么使用 Spring-WS 结合 WSS4J 是一个非常优雅的方案,它将配置与代码解耦,符合 Spring 的设计哲学。

步骤 1:准备依赖

除了 Spring-WS 的依赖,你还需要 WSS4J。

<dependencies>
    <!-- Spring-WS -->
    <dependency>
        <groupId>org.springframework.ws</groupId>
        <artifactId>spring-ws-core</artifactId>
        <version>3.1.2</version>
    </dependency>
    <!-- WSS4J -->
    <dependency>
        <groupId>org.apache.wss4j</groupId>
        <artifactId>wss4j-ws-security-common</artifactId>
        <version>2.4.1</version>
    </dependency>
    <!-- 其他依赖... -->
</dependencies>

步骤 2:配置 Spring Bean

applicationContext.xml 或 Java 配置类中定义 WebServiceTemplateSecurityInterceptor

<!-- applicationContext.xml -->
<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
    <property name="messageSender">
        <bean class="org.springframework.ws.transport.http.HttpComponentsMessageSender"/>
    </property>
    <property name="defaultUri" value="http://your-csharp-webservice-url/YourService.asmx"/>
</bean>
<!-- 安全拦截器配置 -->
<bean id="securityOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
    <constructor-arg>
        <map>
            <entry key="action" value="UsernameToken Timestamp"/>
            <entry key="user" value="your-username"/>
            <entry key="password" value="your-password"/>
            <entry key="passwordType" value="PasswordText"/>
            <entry key="timestampStrict" value="false"/>
        </map>
    </constructor-arg>
</bean>
<!-- 将安全拦截器添加到 WebServiceTemplate -->
<bean id="marshallingWebServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate"
      parent="webServiceTemplate">
    <property name="interceptors">
        <list>
            <ref bean="securityOutInterceptor"/>
        </list>
    </property>
</bean>

步骤 3:编写服务调用代码

你可以通过注入 WebServiceTemplate 来调用服务,而无需关心底层的 SOAP 消息和安全细节。

import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
public class MyServiceClient {
    private final WebServiceTemplate webServiceTemplate;
    public MyServiceClient(WebServiceTemplate webServiceTemplate) {
        this.webServiceTemplate = webServiceTemplate;
    }
    public String callGetUserInfo(String userId) {
        // 创建请求对象 (需要根据 WSDL 生成的 JAXB 类)
        GetUserRequest request = new GetUserRequest();
        request.setUserId(userId);
        // 发送请求并接收响应
        GetUserResponse response = (GetUserResponse) webServiceTemplate.marshalSendAndReceive(
            "http://your-csharp-webservice-url/YourService.asmx", // 可以覆盖 defaultUri
            request,
            new SoapActionCallback("http://tempuri.org/YourService/GetUserInfo") // SOAPAction 头部
        );
        return response.getUserInfo();
    }
}

手动构建 SOAP 消息 (不推荐,仅作为最后手段)

如果由于某些原因(如 WSDL 文件有严重问题,无法生成客户端代码),你不想使用任何框架,可以手动构建 SOAP 消息并使用 HttpURLConnection 发送。

这种方法非常繁琐,容易出错,且难以维护,只适用于非常简单的、无安全要求的 SOAP 服务。

示例代码

import java.io.BufferedReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class ManualSoapClient {
    public static void main(String[] args) {
        String soapEndpointUrl = "http://your-csharp-webservice-url/YourService.asmx";
        String soapAction = "http://tempuri.org/YourService/GetUserInfo"; // 查看WSDL确定
        String xmlRequestString = buildSoapRequest("test-user-id");
        try {
            URL url = new URL(soapEndpointUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Content-Type", "text/xml; charset=utf-8");
            connection.setRequestProperty("SOAPAction", soapAction);
            connection.setDoOutput(true);
            OutputStream out = connection.getOutputStream();
            out.write(xmlRequestString.getBytes());
            out.flush();
            out.close();
            int responseCode = connection.getResponseCode();
            System.out.println("Response Code: " + responseCode);
            if (responseCode == HttpURLConnection.HTTP_OK) {
                BufferedReader in = new BufferedReader(
                    new java.io.InputStreamReader(connection.getInputStream()));
                String inputLine;
                StringBuilder response = new StringBuilder();
                while ((inputLine = in.readLine()) != null) {
                    response.append(inputLine);
                }
                in.close();
                System.out.println("Response XML:");
                System.out.println(response.toString());
                // 这里需要手动解析返回的XML字符串,非常麻烦
            } else {
                System.out.println("POST request failed");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static String buildSoapRequest(String userId) {
        // 注意:这里的命名空间、方法名等必须和 .NET WSDL 中的定义完全一致
        return "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:tem=\"http://tempuri.org/\">" +
                "   <soapenv:Header/>" +
                "   <soapenv:Body>" +
                "      <tem:GetUserInfo>" +
                "         <tem:userId>" + userId + "</tem:userId>" +
                "      </tem:GetUserInfo>" +
                "   </soapenv:Body>" +
                "</soapenv:Envelope>";
    }
}

总结与建议

方法 优点 缺点 适用场景
Apache CXF *功能强大,灵活,对 WS- 标准支持好**,社区活跃,是行业标准之一。 配置相对复杂,需要引入额外的库。 强烈推荐,几乎所有复杂的 Java 调用 .NET WebService 的场景。
Spring-WS 与 Spring 生态无缝集成,配置清晰,关注点分离。 需要 Spring 环境,对于小型项目可能略显笨重。 项目已经基于 Spring,希望利用 IoC 和 AOP 简化开发。
手动构建 无需额外依赖,完全可控。 极其繁琐,难以维护,容易出错,无法处理复杂的安全策略。 仅用于学习或调用非常简单的、无安全要求的遗留 SOAP 服务。

最终建议:

  1. 首选 Apache CXF,它是最健壮、最灵活的解决方案,能够应对绝大多数跨平台 WebService 调用,尤其是涉及复杂安全策略的情况。
  2. 如果你的项目是 Spring 项目,Spring-WS 是一个绝佳的选择,它能让你的代码更优雅。
  3. 尽量避免手动构建 SOAP 消息,除非你完全没有其他选择。

在开始之前,请务必与 .NET 服务的开发人员沟通,获取准确的 WSDL 文件,并确认服务端的安全策略(如是否需要 TimestampPasswordTypeText 还是 Digest 等),这将大大简化你的客户端配置工作。

分享:
扫描分享到社交APP
上一篇
下一篇