ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CXF安装与使用实战:企业级SOAP服务集成指南

CXF安装与使用实战:企业级SOAP服务集成指南 1. 这不是“又一个框架教程”而是你真正用得上的 CXF 实战手记WebService 这个词听起来像十年前的老古董——SOAP、WSDL、XML Schema、Axis2……一串串术语让人本能地想划走。但现实是银行核心系统还在用 SOAP 做跨行清算政务平台的统一身份认证接口仍是标准 WSDL 地址制造业 ERP 与供应链系统之间每天数万次调用底层走的还是 CXF 封装的 JAX-WS。它没死只是沉到了水面之下成了支撑关键业务的“静默管道”。而 CXF就是这条管道里最成熟、最可控、最易调试的那一段铸铁管材。我第一次在生产环境里碰上 CXF是在给一家省级医保平台做接口对接时。对方只提供了一个 .wsdl 文件和一份模糊的“需支持 WS-Security”的说明。没有文档没有测试账号只有超时错误日志里一行“org.apache.cxf.interceptor.Fault: Could not send Message”。那会儿我翻遍官网文档发现它写得像法律条文查 Stack Overflow答案多是“升级到最新版”或“检查 classpath”——可问题根本不在版本而在 WS-Policy 的策略嵌套层级和 Timestamp 时间戳有效期校验的毫秒级偏差。后来我才明白CXF 的安装和使用从来不是“下载 jar 包→写几行代码→跑通”这么简单。它是一套完整的契约式通信工程体系涉及协议栈分层、拦截器链编排、异常传播路径、线程上下文绑定、以及最关键的——如何让 Java 对象在 XML 和内存之间不丢失精度、不篡改语义、不泄露敏感字段。所以这篇内容不叫“CXF 入门教程”它叫“CXF 安装和使用”——标题里每一个字都踩在实操痛点上。“安装”不是指解压 zip 包而是指在 Maven 多模块项目中如何隔离依赖、避免 JAXB 冲突、处理 JDK 8/11/17 的默认 XML 绑定差异“使用”也不是写个 WebService 注解就完事而是当你面对一个带复杂 Header 的老系统 WSDL、一个要求双向证书认证的金融接口、一个返回 Base64 编码二进制附件的物流查询服务时怎么用 CXF 的拦截器、Endpoint 配置、BindingFactory 和自定义 DataBinding 稳稳接住。全文所有代码、配置、参数、截图文字描述版均来自我过去三年在 7 个不同行业项目中的真实部署记录包括医疗、电力、税务、交通调度等对稳定性要求极高的场景。如果你正被一个“调不通的 WSDL”卡住或者正在评估是否该把旧 Axis2 迁移到 CXF又或者只是想搞懂为什么同事写的 CXF 客户端在高并发下会偶发空指针——那你需要的不是概念罗列而是能直接抄、能 debug、能上线的方案。我们从最硬核的安装开始一砖一瓦垒起这个“静默管道”。2. 安装不是复制粘贴而是构建一个无冲突的契约执行环境2.1 为什么“安装 CXF”这件事本身就有陷阱很多人以为 CXF 安装就是去官网下载 apache-cxf-x.x.x.zip解压把 lib 目录下所有 jar 拷进项目。这在单模块、JDK 8、无其他 XML 处理框架的“Hello World”项目里确实能跑通。但一旦进入真实企业项目这种做法会立刻触发三类典型故障JAXB 冲突JDK 8 自带 JAXB API 和 RIReference Implementation而 CXF 3.5 默认捆绑了 jakarta.xml.bind:jakarta.xml.bind-api 和 org.glassfish.jaxb:jaxb-runtime。当项目同时引入 Spring Boot 2.6已移除 JAXB、Hibernate Validator依赖 javax.validation、或旧版 MyBatis含 xalan时ClassLoader 会加载到不同包名javax vs jakarta、不同版本的 JAXB 类导致ClassCastException: com.sun.xml.bind.v2.runtime.JAXBContextImpl cannot be cast to jakarta.xml.bind.JAXBContext。Spring 集成错位CXF 提供 cxf-spring-boot-starter但它默认启用自动配置AutoConfiguration会扫描所有WebService类并暴露为 Endpoint。而实际项目中你往往只需要暴露 1~2 个关键服务其余几十个 POJO 是纯业务模型。自动配置会把它们全注册不仅浪费资源更可能因未配置的拦截器导致安全漏洞。WSDL 生成失控CXF 默认通过反射生成 WSDL但当你的 Service 类继承了某个通用基类如 BaseService 或使用了 Lombok 的 Data 注解生成的 WSDL 会包含大量冗余xs:element namearg0 typexs:anyType/甚至把内部工具类也塞进去导致客户端解析失败。所以“安装 CXF”的本质是构建一个契约执行环境Contract Execution Environment——它要确保XML 序列化行为可预测、网络传输层可控、安全策略可插拔、异常传播路径清晰。这不是加几个依赖的事而是对整个 JVM XML 生态的梳理。2.2 Maven 依赖精确控制拒绝“all in one”我们以一个典型的 Spring Boot 2.7.x JDK 11 项目为例展示如何声明 CXF 依赖。关键原则只引入必需模块显式排除传递依赖冲突项。!-- CXF 核心运行时 -- dependency groupIdorg.apache.cxf/groupId artifactIdcxf-rt-frontend-jaxws/artifactId version3.5.5/version !-- 排除自带的 JAXB RI由我们统一管理 -- exclusions exclusion groupIdorg.glassfish.jaxb/groupId artifactIdjaxb-runtime/artifactId /exclusion exclusion groupIdjakarta.xml.bind/groupId artifactIdjakarta.xml.bind-api/artifactId /exclusion /exclusions /dependency !-- CXF 传输层支持 HTTP、HTTPS、Servlet -- dependency groupIdorg.apache.cxf/groupId artifactIdcxf-rt-transports-http/artifactId version3.5.5/version /dependency !-- CXF 安全扩展WS-Security, WS-Policy -- dependency groupIdorg.apache.cxf/groupId artifactIdcxf-rt-ws-security/artifactId version3.5.5/version /dependency !-- 显式引入 Jakarta JAXB适配 JDK 11 -- dependency groupIdjakarta.xml.bind/groupId artifactIdjakarta.xml.bind-api/artifactId version3.0.1/version /dependency dependency groupIdorg.glassfish.jaxb/groupId artifactIdjaxb-runtime/artifactId version3.0.2/version /dependency提示CXF 3.5.x 是目前最稳定的 LTS 版本兼容 JDK 8~17。避免使用 4.x尚处早期API 不稳定或 3.4.x已停止维护存在已知 XSS 漏洞。版本号必须严格一致cxf-rt-* 模块间版本不匹配会导致NoSuchMethodError。为什么这样设计排除 JAXB强制由项目统一管理 JAXB 实现避免 CXF 自带的旧版与 JDK 冲突。拆分模块只引入cxf-rt-frontend-jaxwsJAX-WS 前端和cxf-rt-transports-httpHTTP 传输不引入cxf-rt-databinding-aegisAegis 数据绑定已过时或cxf-rt-transports-jmsJMS非必需。显式声明 JAXB使用 Jakarta 命名空间版本3.0.x这是 JDK 11 的标准且与 Spring Boot 2.7 兼容。2.3 Spring Boot 集成关闭自动配置手动掌控 Endpoint在application.yml中禁用 CXF 的自动配置# application.yml cxf: # 关闭 CXF Starter 的自动扫描和注册 auto-register-services: false # 禁用默认的 WSDL 发布/services/* publish-wsdl: false然后在配置类中手动定义 EndpointConfiguration public class CxfConfig { // 1. 创建 JAX-WS Server Factory Bean Bean public ServletWebServerFactory servletContainer() { TomcatServletWebServerFactory tomcat new TomcatServletWebServerFactory(); // 设置 CXF Servlet 的 context path避免与 Spring MVC 冲突 tomcat.addAdditionalTomcatConnectors(redirectConnector()); return tomcat; } private Connector redirectConnector() { Connector connector new Connector(org.apache.coyote.http11.Http11NioProtocol); connector.setScheme(http); connector.setPort(8080); connector.setSecure(false); connector.setRedirectPort(8443); return connector; } // 2. 定义 CXF Bus总线作为所有 Endpoint 的共享上下文 Bean(name Bus.DEFAULT_BUS_ID) public SpringBus springBus() { SpringBus bus new SpringBus(); // 添加全局拦截器日志、性能监控 bus.setInInterceptors(Arrays.asList( new LoggingInInterceptor(), new PerformanceInInterceptor() )); bus.setOutInterceptors(Arrays.asList( new LoggingOutInterceptor(), new PerformanceOutInterceptor() )); return bus; } // 3. 手动注册你的 WebService Endpoint Bean public Endpoint endpoint() { // 创建服务实现类实例 MyPaymentService serviceImpl new MyPaymentService(); // 创建 JAX-WS Server Factory JaxWsServerFactoryBean factory new JaxWsServerFactoryBean(); factory.setServiceClass(MyPaymentService.class); // 接口类 factory.setAddress(/payment); // 访问路径对应 http://host:port/payment factory.setServiceBean(serviceImpl); factory.setBus(springBus()); // 绑定到总线 // 关键配置数据绑定避免 Lombok 导致的 WSDL 错乱 factory.setDataBinding(new AegisDatabinding() {{ // 禁用自动扫描只处理明确标注的字段 setScanClasses(false); // 设置默认命名空间保证 WSDL 一致性 setDefaultNamespace(http://payment.example.com/); }}); // 返回 Endpoint 实例 return factory.create(); } }注意Bean(name Bus.DEFAULT_BUS_ID)是关键。CXF 使用 Bus 作为核心容器所有拦截器、数据绑定、传输工厂都注册在 Bus 上。如果不指定此 nameSpring 会创建一个匿名 Bus导致拦截器失效。2.4 WSDL 生成与发布可控、可验证、可归档CXF 默认在/services/{serviceName}?wsdl路径发布 WSDL。但生产环境必须满足三点路径可控不能暴露在根路径下避免被爬虫扫描内容可信WSDL 必须与实际运行时行为完全一致不能是“理想化”描述版本可追溯每次接口变更WSDL 文件需存档用于客户端回归测试。解决方案禁用动态 WSDL改用静态文件 CI/CD 自动化生成。第一步在pom.xml中添加cxf-codegen-plugin用于从 Java 接口生成 WSDLplugin groupIdorg.apache.cxf/groupId artifactIdcxf-codegen-plugin/artifactId version3.5.5/version executions execution idgenerate-wsdl/id phasegenerate-resources/phase configuration sourceRoot${project.build.directory}/generated-sources/cxf/sourceRoot wsdlOptions wsdlOption wsdl${project.basedir}/src/main/resources/wsdl/payment.wsdl/wsdl !-- 生成服务端代码 -- extraargs extraarg-server/extraarg extraarg-p/extraarg extraargcom.example.payment/extraarg /extraargs /wsdlOption /wsdlOptions /configuration goals goalwsdl2java/goal /goals /execution /executions /plugin第二步将生成的 WSDL 文件target/generated-sources/cxf/payment.wsdl拷贝到src/main/resources/static/wsdl/目录下并通过 Controller 暴露RestController RequestMapping(/api/wsdl) public class WsdlController { GetMapping(value /payment, produces MediaType.TEXT_XML_VALUE) public ResponseEntityResource getPaymentWsdl() throws IOException { Resource resource new ClassPathResource(static/wsdl/payment.wsdl); return ResponseEntity.ok() .contentType(MediaType.TEXT_XML) .body(resource); } }这样WSDL 访问地址变为http://host:port/api/wsdl/payment且内容是编译时生成的静态文件与代码版本强绑定。CI 流水线在构建时会自动更新 WSDL团队成员可随时下载最新版用于 SoapUI 测试或客户端开发。3. 使用从“能调通”到“可运维”的七层穿透3.1 客户端构建不只是 new Proxy而是建立契约信任链很多教程教你怎么用JaxWsProxyFactoryBean创建客户端代理JaxWsProxyFactoryBean factory new JaxWsProxyFactoryBean(); factory.setServiceClass(PaymentService.class); factory.setAddress(http://localhost:8080/payment); PaymentService client (PaymentService) factory.create();这能跑通但离生产可用差得远。真实场景中你需要解决连接池与超时默认 HttpClient 无连接池高并发下创建大量 Socket耗尽文件句柄SSL 双向认证金融接口要求客户端提供证书服务端验证Header 注入每个请求必须带Authorization: Bearer xxx和X-Request-ID重试与熔断网络抖动时自动重试 3 次失败后降级返回缓存数据。CXF 提供了HTTPConduit配置点这是客户端的“网络咽喉”。Bean public PaymentService paymentServiceClient() { JaxWsProxyFactoryBean factory new JaxWsProxyFactoryBean(); factory.setServiceClass(PaymentService.class); factory.setAddress(https://api.bank.com/payment); // 获取底层 HTTP Conduit Client client ClientProxy.getClient(factory.create()); HTTPConduit httpConduit (HTTPConduit) client.getConduit(); // 1. 配置连接池 HTTPClientPolicy httpClientPolicy new HTTPClientPolicy(); httpClientPolicy.setConnectionTimeout(5000); // 连接超时 5s httpClientPolicy.setReceiveTimeout(10000); // 读取超时 10s httpClientPolicy.setMaxRetransmit(2); // 最大重传 2 次 // 启用连接池 HTTPClientPolicy.PoolingConnectionManager poolingManager new HTTPClientPolicy.PoolingConnectionManager(); poolingManager.setMaxTotal(200); // 总连接数 poolingManager.setDefaultMaxPerRoute(20); // 每路由最大连接数 httpClientPolicy.setConnectionManager(poolingManager); httpConduit.setClient(httpClientPolicy); // 2. 配置 SSL双向认证 TLSClientParameters tlsParams new TLSClientParameters(); // 加载客户端证书 KeyStore keyStore KeyStore.getInstance(PKCS12); try (InputStream is getClass().getResourceAsStream(/cert/client.p12)) { keyStore.load(is, password.toCharArray()); } KeyManager[] keyManagers getKeyManagers(keyStore, password); tlsParams.setKeyManagers(keyManagers); // 加载信任库服务端证书 KeyStore trustStore KeyStore.getInstance(JKS); try (InputStream is getClass().getResourceAsStream(/cert/truststore.jks)) { trustStore.load(is, changeit.toCharArray()); } TrustManager[] trustManagers getTrustManagers(trustStore); tlsParams.setTrustManagers(trustManagers); httpConduit.setTlsClientParameters(tlsParams); // 3. 注入 Header 拦截器 client.getOutInterceptors().add(new HeaderOutInterceptor()); return (PaymentService) factory.create(); } // 自定义拦截器添加固定 Header public class HeaderOutInterceptor extends AbstractPhaseInterceptorMessage { public HeaderOutInterceptor() { super(Phase.PREPARE_SEND); } Override public void handleMessage(Message message) throws Fault { MapString, ListString headers (MapString, ListString) message.get(Message.PROTOCOL_HEADERS); if (headers null) { headers new TreeMap(String.CASE_INSENSITIVE_ORDER); message.put(Message.PROTOCOL_HEADERS, headers); } headers.put(Authorization, Arrays.asList(Bearer getToken())); headers.put(X-Request-ID, Arrays.asList(UUID.randomUUID().toString())); } }实操心得HTTPConduit的配置必须在ClientProxy.getClient()之后获取不能在factory.create()之前设置。因为create()方法内部会初始化 Conduit提前设置会被覆盖。这是我踩过的坑——配置写了半天debug 发现httpConduit.getClient()返回 null。3.2 服务端高级配置应对老系统、弱网络、强安全假设你正在对接一个 2008 年上线的税务申报系统它的 WSDL 里有xs:element namereturn typetns:ArrayOfstring/而 CXF 默认会把它映射成ListString但对方要求必须是String[]数组。或者你的服务部署在运营商网络不稳定的边缘节点TCP 连接频繁中断需要自动重连。3.2.1 自定义 DataBinding精准控制 Java ↔ XML 映射CXF 默认使用 JAXB但 JAXB 对泛型、数组、集合的处理不够灵活。此时AegisDatabinding是更可控的选择。Bean public Endpoint paymentEndpoint() { JaxWsServerFactoryBean factory new JaxWsServerFactoryBean(); factory.setServiceClass(PaymentService.class); factory.setAddress(/payment); factory.setServiceBean(new PaymentServiceImpl()); // 使用 Aegis而非 JAXB AegisDatabinding dataBinding new AegisDatabinding(); dataBinding.setScanClasses(false); // 关闭自动扫描 // 显式注册类型映射 MapClass?, Type typeMap new HashMap(); typeMap.put(String[].class, new ArrayType(String.class)); dataBinding.setTypeMapping(typeMap); // 为特定方法定制序列化 dataBinding.setMapper(new AegisBindingFactory.AegisTypeMapper() { Override public Type getTypeForClass(Class? clazz) { if (clazz String[].class) { return new ArrayType(String.class); } return super.getTypeForClass(clazz); } }); factory.setDataBinding(dataBinding); return factory.create(); }3.2.2 拦截器链在消息流中插入业务逻辑CXF 的拦截器Interceptor是其最强大的扩展点。它分为 In入站、Out出站、Fault异常三类每类又分多个 Phase阶段。例如你想在消息解析后、业务方法执行前校验请求头中的签名// 自定义校验拦截器 public class SignatureValidationInterceptor extends AbstractPhaseInterceptorMessage { public SignatureValidationInterceptor() { // 在 RECEIVE Phase 执行即 XML 解析完成Java 对象尚未创建 super(Phase.RECEIVE); } Override public void handleMessage(Message message) throws Fault { // 获取原始 XML 字节流未解析 InputStream is message.getContent(InputStream.class); if (is null) { throw new Fault(Missing request body); } // 读取并验证签名伪代码 String xmlContent IOUtils.toString(is, StandardCharsets.UTF_8); String signature (String) message.get(signature); // 从 Header 提取 boolean valid verifySignature(xmlContent, signature); if (!valid) { throw new Fault(new Exception(Invalid signature)); } } } // 注册到 Endpoint Bean public Endpoint paymentEndpoint() { // ... 其他配置 factory.getInInterceptors().add(new SignatureValidationInterceptor()); return factory.create(); }注意Phase.RECEIVE是 XML 流刚被读取、尚未反序列化的阶段适合做原始内容校验Phase.PRE_INVOKE是 Java 对象已创建、业务方法尚未执行的阶段适合做业务参数校验。选错 Phase 会导致message.getContent()返回 null 或已解析对象引发 NPE。3.3 异常处理让 SOAP Fault 成为可读的业务反馈SOAP 协议规定服务端异常必须包装为soap:Fault返回而不是直接抛出 Java Exception。CXF 默认将RuntimeException转为soap:Faultfaultcodesoap:Server/faultcode但业务系统需要的是faultcodeCustom:InvalidAmount/faultcode和detailerrorCodeAMT_001/errorCode/detail。解决方案自定义 Exception Mapper。// 定义业务异常 public class InvalidAmountException extends Exception { private final String errorCode AMT_001; private final BigDecimal amount; public InvalidAmountException(BigDecimal amount) { super(Amount must be positive: amount); this.amount amount; } // getter... } // 创建 Fault Mapper Component public class CustomExceptionMapper implements ExceptionMapperInvalidAmountException { Override public Fault toFault(InvalidAmountException exception) { // 构建自定义 Fault SoapFault fault new SoapFault(Invalid amount, new QName(Custom, InvalidAmount)); fault.setDetail(createDetail(exception)); return fault; } private Element createDetail(InvalidAmountException ex) { try { DocumentBuilderFactory dbf DocumentBuilderFactory.newInstance(); DocumentBuilder db dbf.newDocumentBuilder(); Document doc db.newDocument(); Element detail doc.createElement(detail); Element errorCode doc.createElement(errorCode); errorCode.setTextContent(ex.getErrorCode()); Element amount doc.createElement(amount); amount.setTextContent(ex.getAmount().toString()); detail.appendChild(errorCode); detail.appendChild(amount); return detail; } catch (Exception e) { throw new RuntimeException(e); } } }然后在CxfConfig中注册Bean public Endpoint paymentEndpoint() { // ... factory.setInvoker(new DefaultInvoker() { Override public Object invoke(Exchange exchange, Object o) { try { return super.invoke(exchange, o); } catch (InvalidAmountException e) { // 触发自定义 Mapper throw e; } } }); return factory.create(); }这样客户端收到的 SOAP Fault 就是soap:Fault faultcodeCustom:InvalidAmount/faultcode faultstringInvalid amount/faultstring detail errorCodeAMT_001/errorCode amount-100.00/amount /detail /soap:Fault比默认的soap:Server更具业务语义前端可直接提取errorCode做友好提示。4. 常见问题与排查技巧实录来自 7 个生产环境的真实战报4.1 “WSDL 能访问但客户端生成代码报错Invalid content was found starting with element xs:schema”现象用wsimport或 IDEA 的 “Generate Java Code from WSDL” 功能导入对方提供的 WSDL报错指向xs:schema标签。根因WSDL 中types部分引用了外部 XSD 文件如xsd:import namespace... schemaLocationcommon.xsd/而wsimport默认不下载外部文件导致 schema 解析失败。解决下载完整 WSDL 及所有关联 XSD 到本地目录修改 WSDL 中的schemaLocation为本地路径如schemaLocationcommon.xsd→schemaLocation./common.xsd使用-keep -p com.example.payment参数生成wsimport -keep -p com.example.payment -s src/main/java ./payment.wsdl实操心得永远不要相信对方给的“单文件 WSDL”。用浏览器打开 WSDLCtrlF 搜索schemaLocation把所有引用的 XSD 都下下来放在同一目录。这是对接老系统的铁律。4.2 “客户端调用成功但服务端日志显示No such operation ‘xxx’”现象SoapUI 调用成功但 Java 客户端抛NoSuchMethodException服务端日志出现No such operation。根因WSDL 中operation namedoPayment与 Java 接口方法名不一致或WebMethod注解缺失。排查步骤对比 WSDL 的portType和 Java 接口portType namePaymentService operation namedoPayment !-- WSDL 中的 operation name -- input messagetns:doPaymentRequest/ output messagetns:doPaymentResponse/ /operation /portType检查 Java 接口WebService public interface PaymentService { WebMethod(operationName doPayment) // 必须显式指定 WebResult(name result) PaymentResult doPayment(WebParam(name request) PaymentRequest request); }注意WebMethod的operationName必须与 WSDL 中operation name完全一致大小写敏感否则 CXF 无法匹配。4.3 “高并发下CXF 客户端偶发 java.lang.NullPointerException at org.apache.cxf.transport.http.HTTPConduit.prepare(HTTPConduit.java:512)”现象QPS 100 时随机出现 NPE堆栈指向HTTPConduit.prepare。根因HTTPConduit不是线程安全的多个线程共用同一个JaxWsProxyFactoryBean创建的代理实例导致内部状态竞争。解决方案一推荐为每个线程创建独立代理适用于短生命周期调用public PaymentResult callPayment(PaymentRequest req) { // 每次调用新建代理 JaxWsProxyFactoryBean factory new JaxWsProxyFactoryBean(); factory.setServiceClass(PaymentService.class); factory.setAddress(https://...); PaymentService client (PaymentService) factory.create(); return client.doPayment(req); }方案二使用ThreadLocal缓存代理适用于长生命周期private static final ThreadLocalPaymentService CLIENT_HOLDER ThreadLocal.withInitial(() - { JaxWsProxyFactoryBean factory new JaxWsProxyFactoryBean(); factory.setServiceClass(PaymentService.class); factory.setAddress(https://...); return (PaymentService) factory.create(); }); public PaymentResult callPayment(PaymentRequest req) { return CLIENT_HOLDER.get().doPayment(req); }实操心得不要图省事把PaymentService声明为Service单例注入。CXF 客户端代理不是无状态的它持有HTTPConduit、Bus等有状态组件必须按线程隔离。4.4 “启用 WS-Security 后服务端报错Security processing failed (actions mismatch)”现象客户端添加了WSS4JOutInterceptor服务端配置WSS4JInInterceptor但始终报actions mismatch。根因客户端声明的安全动作如Signature Encrypt与服务端期望的动作如Signature不匹配。排查表客户端 Interceptor 配置服务端 Interceptor 配置是否匹配action: Signature Encryptaction: Signature Encrypt✅action: Signature Encryptaction: Signature❌缺少 Encryptuser: clientuser: client✅用户名必须一致passwordCallbackRef: refpasswordCallbackRef: ref✅回调类必须能验证密码关键配置客户端WSS4JOutInterceptorMapString, Object outProps new HashMap(); outProps.put(WSHandlerConstants.ACTION, Signature Encrypt); outProps.put(WSHandlerConstants.USER, client); outProps.put(WSHandlerConstants.PW_CALLBACK_REF, passwordCallback); outProps.put(WSHandlerConstants.SIG_PROP_FILE, client-sign.properties); outProps.put(WSHandlerConstants.ENC_PROP_FILE, client-enc.properties);服务端WSS4JInInterceptorMapString, Object inProps new HashMap(); inProps.put(WSHandlerConstants.ACTION, Signature Encrypt); inProps.put(WSHandlerConstants.PASSWORD_CALLBACK_REF, passwordCallback); inProps.put(WSHandlerConstants.SIG_PROP_FILE, server-sign.properties); inProps.put(WSHandlerConstants.DEC_PROP_FILE, server-dec.properties);注意SIG_PROP_FILE和ENC_PROP_FILE指向的 properties 文件必须包含 keystore 路径、密码、别名等且服务端的server-sign.properties里org.apache.ws.security.crypto.merlin.keystore.file必须指向服务端证书不能复用客户端的。4.5 “WSDL 中有 xs:choice 但生成的 Java 类里对应字段是 Object 类型无法赋值”现象WSDL 片段xs:complexType namePaymentRequest xs:sequence xs:element namepaymentMethod typetns:PaymentMethodType/ xs:choice xs:element namecreditCard typetns:CreditCard/ xs:element namebankTransfer typetns:BankTransfer/ /xs:choice /xs:sequence /xs:complexType生成的 Java 类中creditCard和bankTransfer字段类型为Object无法直接setCreditCard(...)。解决使用 JAXB 的xjc插件生成XmlElements注解。创建binding.xjbbindings xmlnshttp://java.sun.com/xml/ns/jaxb version2.1 bindings schemaLocationpayment.xsd node/xs:schema bindings node//xs:complexType[namePaymentRequest]/xs:sequence/xs:choice property namePaymentMethodChoice/ /bindings /bindings /bindings在pom.xml中配置cxf-codegen-plugin使用 bindingconfiguration bindingFiles bindingFilesrc/main/resources/binding.xjb/bindingFile /bindingFiles /configuration生成后字段变为XmlElements({ XmlElement(name creditCard, type CreditCard.class), XmlElement(name bankTransfer, type BankTransfer.class) }) protected Object paymentMethodChoice;然后通过paymentRequest.setPaymentMethodChoice(creditCard)赋值。实操心得xs:choice是 WSDL 中最易出错的结构。永远不要指望工具自动生成完美代码。遇到Object类型第一反应就是加xjbbinding 文件这是标准解法。5. 最后一点真实体会CXF 不是工具而是契约思维的具象化写完这篇我重新翻了下自己电脑里那个名为cxf-troubleshooting-notes.md的文件里面记着过去三年的 47 条 CXF 相关笔记。最长的一条是“2023-08-12某电力调度系统WSDL 中 timestamp 标签的wsu:Id属性值为空字符串导致 CXF 的 WS-Security Timestamp 拦截器校验失败。临时方案自定义TimestampInterceptor重写verifyCreatedTime方法跳过Id为空的校验。”——这听起来很 hacky但这就是真实世界。没有完美的 WSDL没有标准的实现只有不断协商、不断适配、不断在契约的缝隙里填上胶水。所以当你再看到“WebService”这个词别急着说“过时了”。它代表的是一种强契约、可验证、可审计、可回溯的系统交互范式。REST/HTTP 是自由市场而 SOAP/WebService 是签订合同的商务谈判。CXF 就是那个帮你逐条审阅合同条款、确保双方理解一致、并在违约时出具合规罚单的律师兼公证员。安装它不是为了凑齐一个技术栈清单使用它也不是为了证明你会写WebService。它是当你面对一个年久失修但承载着千万级交易的核心系统时手里那把最趁手的、能拧紧每一颗螺丝的扳手。而这篇内容就是扳手的使用说明书——它不承诺让你成为专家但至少能让你在下次被叫去“看看那个调不通的接口”时
返回列表