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

下面我将为你详细讲解几种主流的方法,从最推荐到备选方案,并提供代码示例。
核心挑战
在开始之前,你需要了解 Java 调用 .NET WebService 主要面临的几个挑战:
- WS-Security (特别是 UsernameToken 和 Timestamp):这是最常见的问题。.NET WebService 经常要求客户端提供用户名和密码进行身份验证,并且消息中需要包含时间戳以防止重放攻击,标准的
wsimport生成的代码可能无法直接支持这种复杂的策略。 - 消息格式和编码:虽然 SOAP 消息标准是统一的,但 .NET 和 Java 在处理默认的命名空间、SOAPAction 头部、字符集等方面可能存在细微差别。
- WSDL 文件:.NET 的 WSDL 文件有时会包含 Java 客户端工具不支持的特性或引用了 .NET 特有的扩展。
使用 Apache CXF (强烈推荐)
Apache CXF 是一个功能强大、灵活的开源 WebService 框架,它对 WS-Security 等标准有非常好的支持,并且提供了比标准 wsimport 更多的配置选项,是处理跨平台 WebService 调用的首选。
步骤 1:准备环境
确保你的项目中包含了 CXF 的核心依赖,如果你使用 Maven,可以添加以下依赖:

<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 服务需要 UsernameToken 和 Timestamp。

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中的配置添加安全头(如UsernameToken和Timestamp)。WSHandlerConstants.ACTION: 定义了要执行的安全操作,这里是USERNAME_TOKEN和TIMESTAMP的组合。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 配置类中定义 WebServiceTemplate 和 SecurityInterceptor。
<!-- 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 服务。 |
最终建议:
- 首选 Apache CXF,它是最健壮、最灵活的解决方案,能够应对绝大多数跨平台 WebService 调用,尤其是涉及复杂安全策略的情况。
- 如果你的项目是 Spring 项目,Spring-WS 是一个绝佳的选择,它能让你的代码更优雅。
- 尽量避免手动构建 SOAP 消息,除非你完全没有其他选择。
在开始之前,请务必与 .NET 服务的开发人员沟通,获取准确的 WSDL 文件,并确认服务端的安全策略(如是否需要 Timestamp,PasswordType 是 Text 还是 Digest 等),这将大大简化你的客户端配置工作。
