升级JAXB至2.3.0+后Apache CXF SOAP响应解组报错求助
JAXB升级到2.3.0+后SOAP解组命名空间问题解决办法
问题说明
把JAXB从2.2.11升到2.3.0及以上版本后,解析SOAP响应时直接报错:
javax.xml.ws.soap.SOAPFaultException: Unmarshalling Error: unexpected element (uri:"", local:"MessageKey"). Expected elements are <{http://some.namespace}MessageKey>
说白了就是解组时认不出命名空间。虽然改WSDL里的elementFormDefault="unqualified"能搞定,但我们不想动WSDL。已经用SOAPUI确认过响应确实带命名空间,而且用JAXB 2.2.11及以下版本时完全正常。
当前的依赖配置如下:
<org-apache-cxf.version>3.5.0</org-apache-cxf.version> <org-sun-jaxb.version>2.3.0</org-sun-jaxb.version> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>${org-apache-cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>${org-apache-cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-core</artifactId> <version>${org-apache-cxf.version}</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>${org-sun-jaxb.version}</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>${org-sun-jaxb.version}</version> </dependency>
试过各种依赖组合甚至最新版本,问题还是存在。自己试过写自定义解组,但希望有更简单的办法,下面给几个可行的思路:
解决方向
1. 统一JAXB依赖版本,排除冲突
CXF本身会自带JAXB相关依赖,升级后可能和手动引入的版本打架。先加上jaxb-api依赖,版本和jaxb-impl保持一致,再把CXF里自带的JAXB依赖排除掉:
<!-- 添加上jaxb-api --> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <!-- 修改CXF依赖,排除自带的JAXB --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>${org-apache-cxf.version}</version> <exclusions> <exclusion> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> </exclusion> <exclusion> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> </exclusion> </exclusions> </dependency>
其他CXF依赖也可以照此排除,确保所有JAXB相关依赖都是指定的2.3.x版本。
2. 配置JAXB忽略命名空间差异
JAXB 2.3+对命名空间的校验更严格,可以给CXF客户端添加JAXB属性,让解组时忽略命名空间不匹配的问题:
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); factory.setAddress("你的服务地址"); factory.setServiceClass(你的服务接口类.class); // 添加JAXB解组属性 Map<String, Object> jaxbProps = new HashMap<>(); jaxbProps.put("com.sun.xml.bind.unmarshaller.ignore-namespace", true); factory.setProperties(jaxbProps); // 创建客户端 你的服务接口 client = (你的服务接口) factory.create();
3. 自定义命名空间映射
如果上面的方法不行,可以写一个自定义的NamespacePrefixMapper,把空命名空间映射到WSDL里的目标命名空间:
import com.sun.xml.bind.marshaller.NamespacePrefixMapper; public class CustomNamespaceMapper extends NamespacePrefixMapper { @Override public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) { // 把目标命名空间映射为空前缀,对应响应里的无命名空间元素 if ("http://some.namespace".equals(namespaceUri)) { return ""; } return suggestion; } }
然后在CXF客户端配置里设置这个映射器:
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); factory.setAddress("你的服务地址"); factory.setServiceClass(你的服务接口类.class); Map<String, Object> props = new HashMap<>(); props.put("com.sun.xml.bind.namespacePrefixMapper", new CustomNamespaceMapper()); factory.setProperties(props); 你的服务接口 client = (你的服务接口) factory.create();
4. 用SAX过滤器修改命名空间
如果响应里的元素确实是空命名空间,但WSDL定义的是带命名空间的,可以用SAX过滤器在解组前把元素的命名空间改成目标值:
import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.XMLFilterImpl; public class NamespaceFixFilter extends XMLFilterImpl { private final String targetNamespace; public NamespaceFixFilter(String targetNamespace) { this.targetNamespace = targetNamespace; } @Override public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { // 把所有元素的命名空间替换成目标值 super.startElement(targetNamespace, localName, qName, atts); } }
然后在解组时使用这个过滤器:
import javax.xml.bind.JAXBContext; import javax.xml.bind.Unmarshaller; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.XMLReader; // 创建JAXB上下文 JAXBContext jaxbContext = JAXBContext.newInstance(你的响应类.class); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); // 设置SAX过滤器 SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setNamespaceAware(true); SAXParser saxParser = spf.newSAXParser(); XMLReader xmlReader = saxParser.getXMLReader(); NamespaceFixFilter filter = new NamespaceFixFilter("http://some.namespace"); filter.setParent(xmlReader); unmarshaller.setProperty("org.xml.sax.XMLReader", filter); // 解组响应 你的响应类 response = (你的响应类) unmarshaller.unmarshal(响应输入流);
总结
先试依赖排除和版本统一,这是最省事的;不行就用CXF的JAXB属性配置;再不行就用命名空间映射;最后才考虑SAX过滤器。
内容的提问来源于stack exchange,提问作者Jurgen
相关产品推荐
相关产品推荐

