CXF下跨WSDL targetNamespace请求转换的简洁实现方案咨询
刚好之前处理过类似的多命名空间WSDL兼容场景,给你几个简洁靠谱的实现方案,你可以根据自己的项目情况选择:
方案1:用MapStruct实现类型安全的对象映射
这是我最推荐的方案,因为它类型安全、编译时生成映射代码,性能优异,而且后续结构变更时维护成本低。由于wsdl2java生成的类都是标准JAXB注解的POJO,MapStruct可以自动识别同名属性完成映射,只需要处理WSDL2新增的元素即可。
步骤:
- 添加MapStruct依赖到你的
pom.xml:
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.5.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.5.Final</version> <scope>provided</scope> </dependency>
- 定义映射接口:
假设wsdl1生成的类在com.wsdl1.gen包,wsdl2的在com.wsdl2.gen包,创建一个映射器接口:
@Mapper(componentModel = "spring") // 如果用Spring容器管理,否则用default public interface Wsdl1ToWsdl2Mapper { // 静态实例,方便直接调用 Wsdl1ToWsdl2Mapper INSTANCE = Mappers.getMapper(Wsdl1ToWsdl2Mapper.class); // 映射主请求对象 com.wsdl2.gen.Request mapWsdl1ToWsdl2(com.wsdl1.gen.Request wsdl1Request); // 嵌套对象的映射MapStruct会自动处理,不用手动写,除非有特殊属性 com.wsdl2.gen.UserInfo mapUserInfo(com.wsdl1.gen.UserInfo userInfo); }
- 业务代码中使用:
// 接收WSDL1命名空间的请求对象 com.wsdl1.gen.Request wsdl1Request = ...; // 自动映射成WSDL2的对象 com.wsdl2.gen.Request wsdl2Request = Wsdl1ToWsdl2Mapper.INSTANCE.mapWsdl1ToWsdl2(wsdl1Request); // 填充WSDL2独有的元素 wsdl2Request.setNewOptionalField("默认值"); wsdl2Request.setRequiredNewField(getRequiredValueFromSomewhere());
优点:
- 类型安全,编译时就会检查属性映射是否正确
- 自动生成高效的映射代码,性能比反射类工具好
- 结构变更时,只需要修改映射接口即可
方案2:JAXB序列化+命名空间替换+反序列化
如果不想引入新的依赖,这个方案可以直接利用JAXB的序列化/反序列化能力,通过修改XML中的命名空间来完成转换。
步骤:
import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import java.io.StringReader; import java.io.StringWriter; // 1. 初始化JAXB上下文,对应两个命名空间的生成类 JAXBContext wsdl1Context = JAXBContext.newInstance(com.wsdl1.gen.Request.class); JAXBContext wsdl2Context = JAXBContext.newInstance(com.wsdl2.gen.Request.class); // 2. 将WSDL1对象序列化为XML字符串 Marshaller marshaller = wsdl1Context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); StringWriter writer = new StringWriter(); marshaller.marshal(wsdl1Request, writer); String originalXml = writer.toString(); // 3. 替换XML中的命名空间(注意要替换所有出现的WSDL1命名空间) String wsdl1Namespace = "http://your-wsdl1-namespace-url.com"; String wsdl2Namespace = "http://your-wsdl2-namespace-url.com"; String updatedXml = originalXml.replaceAll(wsdl1Namespace, wsdl2Namespace); // 4. 将修改后的XML反序列化为WSDL2对象 Unmarshaller unmarshaller = wsdl2Context.createUnmarshaller(); com.wsdl2.gen.Request wsdl2Request = (com.wsdl2.gen.Request) unmarshaller.unmarshal(new StringReader(updatedXml)); // 5. 填充WSDL2新增元素 wsdl2Request.setNewElement("xxx");
注意点:
- 确保命名空间的替换是全面的,比如XML根元素、子元素、属性中的命名空间都要覆盖
- 如果XML中使用了不同的命名空间前缀,可能需要更精确的正则表达式,比如匹配
xmlns:ns1="wsdl1-ns"这种格式
方案3:CXF拦截器修改SOAP请求命名空间
如果是在CXF服务端接收请求,可以通过自定义拦截器,在CXF解析请求之前修改SOAP消息的命名空间,让CXF直接解析成WSDL2的对象,业务代码只需要补充新增元素即可。
步骤:
- 自定义拦截器:
import org.apache.cxf.binding.soap.SoapMessage; import org.apache.cxf.interceptor.AbstractSoapInterceptor; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.phase.Phase; import org.apache.commons.io.IOUtils; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; public class NamespaceRewriteInterceptor extends AbstractSoapInterceptor { private static final String WSDL1_NS = "http://your-wsdl1-namespace.com"; private static final String WSDL2_NS = "http://your-wsdl2-namespace.com"; public NamespaceRewriteInterceptor() { super(Phase.PRE_PROTOCOL); // 在CXF解析SOAP协议之前执行 } @Override public void handleMessage(SoapMessage message) throws Fault { try { // 读取SOAP请求内容 InputStream inputStream = message.getContent(InputStream.class); String soapXml = IOUtils.toString(inputStream, StandardCharsets.UTF_8); // 替换命名空间 String updatedSoapXml = soapXml.replaceAll(WSDL1_NS, WSDL2_NS); // 将修改后的XML放回消息中 message.setContent(InputStream.class, new ByteArrayInputStream(updatedSoapXml.getBytes(StandardCharsets.UTF_8))); } catch (Exception e) { throw new Fault(new RuntimeException("Failed to rewrite SOAP namespace", e)); } } }
- 注册拦截器:
如果是Spring配置:
<jaxws:endpoint id="wsdl2Service" implementor="com.your.service.Wsdl2ServiceImpl" address="/wsdl2"> <jaxws:inInterceptors> <bean class="com.your.interceptor.NamespaceRewriteInterceptor"/> </jaxws:inInterceptors> </jaxws:endpoint>
如果是代码注册:
import org.apache.cxf.jaxws.EndpointImpl; import javax.xml.ws.Endpoint; EndpointImpl endpoint = (EndpointImpl) Endpoint.publish("/wsdl2", new Wsdl2ServiceImpl()); endpoint.getInInterceptors().add(new NamespaceRewriteInterceptor());
优点:
- 对业务代码完全无侵入,业务代码直接处理WSDL2的对象
- 所有请求的命名空间转换都在拦截器中统一处理
内容的提问来源于stack exchange,提问作者Saad2018
相关产品推荐
相关产品推荐

