Spring WS不使用package-info.java自定义XML命名空间前缀报错如何解决
解决方案
问题根因
Spring 5.x的Jaxb2Marshaller默认会对传入的JAXB属性做合法性校验,仅支持JAXB规范定义的标准属性,Oracle/Sun内部JAXB实现的私有属性com.sun.xml.bind.namespacePrefixMapper会被拦截,因此抛出不支持的异常。另外不同JAXB实现对应的属性key存在差异,也会导致配置不生效。
可行方案1:使用独立JAXB实现并调整属性配置(优先推荐)
Spring Boot 2.2.x默认没有内置独立JAXB依赖,首先引入官方GlassFish JAXB实现,避免使用JDK内置的内部实现:
<!-- pom.xml添加依赖 --> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.3</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.3.3</version> </dependency>
然后自定义NamespacePrefixMapper实现类,注意要继承com.sun.xml.bind.marshaller.NamespacePrefixMapper(不要使用JDK内部的com.sun.xml.internal.bind.marshaller.NamespacePrefixMapper):
public class CustomNamespacePrefixMapper extends NamespacePrefixMapper { @Override public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) { // 按照你的实际命名空间和前缀对应关系配置 if("http://your.namespace.uri.for.ns5".equals(namespaceUri)) { return "ns5"; } else if("http://your.namespace.uri.for.ns6".equals(namespaceUri)) { return "ns6"; } else if("http://your.namespace.uri.for.ns8".equals(namespaceUri)) { return "ns8"; } return suggestion; } }
最后配置Jaxb2Marshaller的时候,关闭属性校验,同时使用GlassFish实现对应的属性key:
@Bean public Jaxb2Marshaller jaxb2Marshaller() { Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setContextPath("你的JAXB生成类包路径"); // 关闭属性合法性校验,允许传入私有实现属性 marshaller.setCheckForXmlRootElement(false); Map<String, Object> properties = new HashMap<>(); // GlassFish JAXB实现对应的属性key,和JDK内部实现的key不同 properties.put(com.sun.xml.bind.marshaller.MarshallerConstants.NAMESPACE_PREFIX_MAPPER, new CustomNamespacePrefixMapper()); marshaller.setMarshallerProperties(properties); return marshaller; }
可行方案2:直接修改WebServiceMessage的输出(兼容受限场景)
如果不想引入额外JAXB依赖,可以通过Spring WS的EndpointInterceptor在响应输出前修改命名空间前缀:
public class NamespacePrefixInterceptor implements EndpointInterceptor { @Override public boolean handleResponse(MessageContext messageContext, Object endpoint) throws Exception { if (messageContext.getResponse() instanceof SaajSoapMessage) { SaajSoapMessage soapMessage = (SaajSoapMessage) messageContext.getResponse(); SOAPMessage saajMsg = soapMessage.getSaajMessage(); SOAPBody body = saajMsg.getSOAPBody(); // 遍历body下的所有元素,修改命名空间前缀 NodeList childNodes = body.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node node = childNodes.item(i); if (node instanceof Element) { Element elem = (Element) node; // 按照实际的命名空间和前缀对应关系修改 if (elem.getNamespaceURI().equals("http://your.namespace.uri.for.ns5")) { elem.setPrefix("ns5"); } // 递归处理子元素,根据实际需求调整 modifyChildPrefixes(elem); } } saajMsg.saveChanges(); } return true; } private void modifyChildPrefixes(Element parent) { NodeList childNodes = parent.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node node = childNodes.item(i); if (node instanceof Element) { Element elem = (Element) node; // 匹配命名空间修改前缀 if("http://your.namespace.uri.for.ns6".equals(elem.getNamespaceURI())) { elem.setPrefix("ns6"); } else if("http://your.namespace.uri.for.ns8".equals(elem.getNamespaceURI())) { elem.setPrefix("ns8"); } modifyChildPrefixes(elem); } } } @Override public boolean handleRequest(MessageContext messageContext, Object endpoint) throws Exception { return true; } @Override public boolean handleFault(MessageContext messageContext, Object endpoint) throws Exception { return true; } @Override public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) throws Exception { } }
然后把这个拦截器注册到Spring WS配置中:
@Configuration @EnableWs public class WebServiceConfig extends WsConfigurerAdapter { @Override public void addInterceptors(List<EndpointInterceptor> interceptors) { interceptors.add(new NamespacePrefixInterceptor()); } }
注意事项
- 两种方案都需要先梳理清楚原服务中每个前缀对应的命名空间URI,确保映射关系正确
- 方案1性能更好,无需额外修改XML结构,优先推荐使用
- 方案2兼容性更强,不需要调整JAXB相关配置,适合依赖调整受限的场景
内容的提问来源于stack exchange,提问作者emeraldjava
相关产品推荐
相关产品推荐

