如何使用OpenFeign将Java 8 SOAP调用升级至Java 11
嘿,我刚好处理过类似的 Java 11 迁移 + OpenFeign 调用 SOAP 1.2 服务的场景,你的 400 错误其实是两个核心问题导致的,咱们一步步拆解解决:
核心问题分析
1. SOAP 信封命名空间不匹配
你提到生成的信封用了 http://schemas.xmlsoap.org/soap/envelope/,这是SOAP 1.1 的命名空间,但目标服务是 SOAP 1.2,对应的标准命名空间应该是 http://www.w3.org/2003/05/soap-envelope。服务端会严格校验信封的命名空间,不匹配直接返回 400。
2. 缺少 WS-Addressing(WSA)规范的 Header
目标服务要求的 wsa 头(比如 wsa:To、wsa:Action、wsa:MessageID)是 SOAP 服务的标准寻址字段,OpenFeign 默认不会自动生成这些内容,必须手动封装到 SOAP 信封的 Header 部分。
具体修复方案
步骤1:定义符合 SOAP 1.2 规范的信封实体
先手动封装 SOAP 1.2 的信封结构,包含 WSA Header 和请求体:
import jakarta.xml.bind.annotation.*; // SOAP 1.2 信封根节点 @XmlRootElement(name = "Envelope", namespace = "http://www.w3.org/2003/05/soap-envelope") @XmlNamespace(prefix = "soap", namespaceURI = "http://www.w3.org/2003/05/soap-envelope") public class Soap12Envelope<T> { @XmlElement(name = "Header", namespace = "http://www.w3.org/2003/05/soap-envelope") private SoapWsaHeader header; @XmlElement(name = "Body", namespace = "http://www.w3.org/2003/05/soap-envelope") private T body; // 构造器、Getter/Setter public Soap12Envelope() {} public Soap12Envelope(SoapWsaHeader header, T body) { this.header = header; this.body = body; } } // WSA Header 实体 @XmlRootElement(name = "Header", namespace = "http://www.w3.org/2005/08/addressing") @XmlNamespace(prefix = "wsa", namespaceURI = "http://www.w3.org/2005/08/addressing") public class SoapWsaHeader { @XmlElement(name = "To", namespace = "http://www.w3.org/2005/08/addressing") private String to; @XmlElement(name = "Action", namespace = "http://www.w3.org/2005/08/addressing") private String action; @XmlElement(name = "MessageID", namespace = "http://www.w3.org/2005/08/addressing") private String messageId; // 构造器、Getter/Setter public SoapWsaHeader(String to, String action, String messageId) { this.to = to; this.action = action; this.messageId = messageId; } }
步骤2:配置 Feign 接口与编码器
更新 Feign 接口,指定正确的 Content-Type,并使用自定义的 SOAP 1.2 信封作为请求体:
import feign.Headers; import feign.RequestLine; @Headers({ "Content-Type: application/soap+xml;charset=UTF-8", "Accept: application/xop+xml;charset=UTF-8" }) public interface ZalogujSoapClient { @RequestLine("POST /your-target-soap-endpoint") String sendZalogujRequest(Soap12Envelope<Zaloguj> request); }
如果是 Spring Cloud OpenFeign,需要配置自定义 JAXB 编码器,确保序列化时使用 SOAP 1.2 的命名空间和编码:
import feign.codec.Encoder; import feign.jaxb.JAXBContextFactory; import feign.jaxb.JaxbEncoder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FeignSoapConfig { @Bean public Encoder feignSoapEncoder() { JAXBContextFactory contextFactory = new JAXBContextFactory.Builder() .withMarshallerJAXBEncoding("UTF-8") .withMarshallerSchemaLocation( "http://www.w3.org/2003/05/soap-envelope", "http://www.w3.org/TR/2003/REC-soap12-part1-20030624/soap12-part1.xsd" ) .build(); return new JaxbEncoder(contextFactory); } }
步骤3:构造包含 WSA 头的请求并调用
在业务代码中,组装完整的 SOAP 请求:
import java.util.UUID; // 构造 WSA 头(参数要和服务端定义的完全匹配) SoapWsaHeader wsaHeader = new SoapWsaHeader( "http://your-target-service-url/endpoint", // wsa:To 目标服务地址 "http://your-service-namespace/Zaloguj", // wsa:Action 服务操作的命名空间 "urn:uuid:" + UUID.randomUUID().toString() // wsa:MessageID 唯一请求标识 ); // 构造业务请求体 Zaloguj zalogujBody = new Zaloguj(); zalogujBody.setUsername("your-username"); zalogujBody.setPassword("your-password"); // 其他参数设置... // 封装成 SOAP 1.2 信封 Soap12Envelope<Zaloguj> soapRequest = new Soap12Envelope<>(wsaHeader, zalogujBody); // 调用 Feign 客户端 String soapResponse = zalogujSoapClient.sendZalogujRequest(soapRequest);
步骤4:处理 MTOM 格式的响应
服务端返回的 application/xop+xml 是 MTOM 格式(用于优化二进制数据传输),OpenFeign 默认解码器无法解析,需要自定义解码器支持 MTOM:
import feign.codec.Decoder; import jakarta.xml.bind.JAXBContext; import jakarta.xml.bind.Unmarshaller; import jakarta.xml.transform.stream.StreamSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.io.InputStream; @Configuration public class FeignMtomDecoderConfig { @Bean public Decoder feignMtomDecoder() { return (response, type) -> { try (InputStream inputStream = response.body().asInputStream()) { JAXBContext jaxbContext = JAXBContext.newInstance(YourResponseEntity.class); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); // 开启 MTOM 支持(依赖 JAXB 实现,比如 Metro) unmarshaller.setProperty("com.sun.xml.bind.mtom.enabled", true); return unmarshaller.unmarshal(new StreamSource(inputStream)); } catch (Exception e) { throw new RuntimeException("解析 MTOM 响应失败", e); } }; } }
调试建议
开启 Feign 全量日志,确认生成的 SOAP 请求是否符合要求:
import feign.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FeignLogConfig { @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } }
日志会打印完整的请求 XML,你可以直接检查命名空间和 WSA 头是否正确。
内容的提问来源于stack exchange,提问作者Piotr Żak

