Spring Boot SOAP客户端MTOM附件Content-ID编码不匹配问题咨询
问题根因判断
是的,该编码差异就是服务端报错的直接原因。
虽然URI规范中@属于需要编码的保留字符,转换为%40符合URI编码规则,但绝大多数SOAP服务端的XOP实现不会对cid:开头的href属性做URI解码操作,会直接拿编码后的字符串去匹配多部分请求里的Content-ID原始值,匹配失败就判定二进制附件未上传。
解决方案(针对Spring Boot 2.5.5 + Spring-WS技术栈)
方案一:切换为Axiom消息工厂(推荐)
Spring-WS默认使用的SAAJ消息工厂会强制对所有URI属性做编码,而Axiom消息工厂对XOP的处理更灵活,不会对cid值里的@做强制编码,是最简便的解决方案。
- 首先引入Axiom依赖(pom.xml):
<dependency> <groupId>org.apache.ws.commons.axiom</groupId> <artifactId>axiom-api</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.ws.commons.axiom</groupId> <artifactId>axiom-impl</artifactId> <version>1.4.0</version> </dependency>
- 替换SOAP消息工厂配置:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.ws.soap.axiom.AxiomSoapMessageFactory; import org.springframework.ws.client.core.WebServiceTemplate; @Configuration public class SoapClientConfig { @Bean public AxiomSoapMessageFactory soapMessageFactory() { AxiomSoapMessageFactory messageFactory = new AxiomSoapMessageFactory(); messageFactory.setPayloadCaching(false); // 开启XOP支持 messageFactory.setXopSupportEnabled(true); return messageFactory; } @Bean public WebServiceTemplate webServiceTemplate(AxiomSoapMessageFactory messageFactory) { WebServiceTemplate template = new WebServiceTemplate(messageFactory); // 补充你原有WebServiceTemplate的其他配置:默认接口地址、Marshaller、Unmarshaller等 return template; } }
方案二:自定义请求拦截器修正href编码
如果受项目约束不能切换Axiom,可以通过客户端拦截器在请求发送前,手动把XOP Include节点href里的%40替换回@:
- 实现自定义拦截器:
import org.springframework.ws.client.support.interceptor.ClientInterceptor; import org.springframework.ws.context.MessageContext; import org.springframework.ws.soap.saaj.SaajSoapMessage; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; public class XopHrefFixInterceptor implements ClientInterceptor { @Override public boolean handleRequest(MessageContext messageContext) throws Exception { SaajSoapMessage soapMessage = (SaajSoapMessage) messageContext.getRequest(); XPath xpath = XPathFactory.newInstance().newXPath(); // 匹配所有xop:Include节点 NodeList xopIncludes = (NodeList) xpath.evaluate( "//*[local-name()='Include' and namespace-uri()='http://www.w3.org/2004/08/xop/include']", soapMessage.getDocument(), XPathConstants.NODESET ); for (int i = 0; i < xopIncludes.getLength(); i++) { Element includeNode = (Element) xopIncludes.item(i); String href = includeNode.getAttribute("href"); if (href.startsWith("cid:") && href.contains("%40")) { includeNode.setAttribute("href", href.replace("%40", "@")); } } return true; } @Override public boolean handleResponse(MessageContext messageContext) { return true; } @Override public boolean handleFault(MessageContext messageContext) { return true; } @Override public void afterCompletion(MessageContext messageContext, Exception ex) { } }
- 把拦截器注册到WebServiceTemplate:
@Bean public WebServiceTemplate webServiceTemplate(SaajSoapMessageFactory messageFactory) { WebServiceTemplate template = new WebServiceTemplate(messageFactory); // 注册自定义拦截器 template.setInterceptors(new ClientInterceptor[]{new XopHrefFixInterceptor()}); // 其他原有配置 return template; }
方案三:自定义Content-ID生成规则
也可以直接修改附件的Content-ID生成逻辑,避免使用@字符,从根源上消除编码差异。
内容的提问来源于stack exchange,提问作者Hosszuful
相关产品推荐
相关产品推荐

