You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从CXF 3.5.7迁移至4.1.0后SOAP修改失效,报cl空指针异常

问题

将项目从Apache CXF 3.5.7迁移至4.1.0版本后,自定义SOAP消息修改拦截器触发空指针异常,移除拦截器后系统恢复正常。异常栈如下:

java.lang.NullPointerException: NullPointerException invoking http://test:4004/se/tm/cc: Cannot invoke "java.net.http.HttpClient.sendAsync(java.net.http.HttpRequest, java.net.http.HttpResponse$BodyHandler)" because "cl" is null
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
    at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.mapException(HTTPConduit.java:1452)
    at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1438)
    at org.apache.cxf.transport.http.HttpClientHTTPConduit$HttpClientWrappedOutputStream.close(HttpClientHTTPConduit.java:824)
    at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:307)
    at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:530)
    at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:441)
    at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:356)
    at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:314)
    at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:334)
    at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:320)
    at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:307)
    at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:121)
    at org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:265)
    at org.apache.cxf.transport.servlet.ServletController.invokeDestination(ServletController.java:233)
    at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:207)
    at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:159)
    at org.apache.cxf.transport.servlet.CXFNonSpringServlet.invoke(CXFNonSpringServlet.java:224)
    at org.apache.cxf.transport.servlet.AbstractHTTPServlet.handleRequest(AbstractHTTPServlet.java:312)
    at org.apache.cxf.transport.servlet.AbstractHTTPServlet.doPost(AbstractHTTPServlet.java:217)
    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)

自定义拦截器代码:

public class OutPreStreamInterceptor extends AbstractPhaseInterceptor<Message>{


    public OutPreStreamInterceptor(String phase) {
        super(phase);
        addBefore(SoapPreProtocolOutInterceptor.class.getName());
    }

    @Override
    public void handleMessage(Message message) {
        OutputStream os = message.getContent(OutputStream.class);
        boolean isOutbound = message == message.getExchange().getOutMessage()
                || message == message.getExchange().getOutFaultMessage();

        String content = getRawContent(message);
        String newContent = content;
        if (isOutbound) {

            InterceptorUtils.CachedStream cs = new InterceptorUtils.CachedStream();
            message.setContent(OutputStream.class, cs);
            String action = retrieveSOAPAction(message);
            if (action.contains("test") ) {
                newContent = content.replace("<ns2:deviceType>tt</ns2:deviceType>", "");
            }
            if (action.contains("test2")) {
                newContent = changeJobId(newContent);
            }
            if (!StringUtils.equalsIgnoreCase(newContent, content)) {
                log.debug("0;Outbound message has been changed: " + newContent);
            }
            try {
                replaceContent(message, newContent, os);
            } catch (IOException e) {
                log.error("0;Error while replacing outbound message {}", e.getMessage());
                throw new RuntimeException("Error while replacing outbound message" + e);
            }
        } else {
            // If necessary, in-bound message may be changed here.
        }
    }

      public static void replaceContent(Message message, String newContent, OutputStream os) throws IOException {
        InputStream replaceInStream = new ByteArrayInputStream(newContent.getBytes(StandardCharsets.UTF_8));

        IOUtils.copy(replaceInStream, os);
        replaceInStream.close();

        os.flush();
        message.setContent(OutputStream.class, os);
        os.close();
}
解决方案

1. 修正OutputStream生命周期管理

CXF 4.x对传输层流的生命周期管控更严格,禁止自定义拦截器提前关闭原始输出流。修改replaceContent方法,移除os.close()调用:

public static void replaceContent(Message message, String newContent, OutputStream os) throws IOException {
    InputStream replaceInStream = new ByteArrayInputStream(newContent.getBytes(StandardCharsets.UTF_8));
    IOUtils.copy(replaceInStream, os);
    replaceInStream.close();
    os.flush();
    // 移除os.close(),由CXF框架负责流的最终关闭
    message.setContent(OutputStream.class, os);
}

2. 正确使用CachedStream处理内容修改

原始代码中缓存流的使用逻辑错误,未让框架先将内容写入缓存流再进行修改。调整拦截器核心逻辑:

@Override
public void handleMessage(Message message) {
    boolean isOutbound = message == message.getExchange().getOutMessage()
            || message == message.getExchange().getOutFaultMessage();

    if (isOutbound) {
        OutputStream originalOs = message.getContent(OutputStream.class);
        InterceptorUtils.CachedStream cs = new InterceptorUtils.CachedStream();
        message.setContent(OutputStream.class, cs);

        try {
            // 触发后续拦截器将内容写入缓存流
            cs.flush();
            // 读取缓存流中的原始内容
            String content = IOUtils.toString(cs.getInputStream(), StandardCharsets.UTF_8.name());
            String newContent = content;

            String action = retrieveSOAPAction(message);
            if (action.contains("test")) {
                newContent = content.replace("<ns2:deviceType>tt</ns2:deviceType>", "");
            }
            if (action.contains("test2")) {
                newContent = changeJobId(newContent);
            }

            if (!StringUtils.equalsIgnoreCase(newContent, content)) {
                log.debug("0;Outbound message has been changed: " + newContent);
            }

            // 将修改后的内容写入原始输出流
            originalOs.write(newContent.getBytes(StandardCharsets.UTF_8));
            originalOs.flush();

            // 清理缓存流并恢复原始输出流
            cs.close();
            message.setContent(OutputStream.class, originalOs);
        } catch (IOException e) {
            log.error("0;Error while replacing outbound message {}", e.getMessage());
            throw new RuntimeException("Error while replacing outbound message", e);
        }
    } else {
        // 入站消息处理逻辑(如有)
    }
}

3. 确认拦截器阶段配置

确保拦截器使用Phase.PRE_STREAM阶段,且在SoapPreProtocolOutInterceptor之前执行,修改构造方法:

public OutPreStreamInterceptor() {
    super(Phase.PRE_STREAM);
    addBefore(SoapPreProtocolOutInterceptor.class.getName());
}

4. 适配Jakarta EE API变化

CXF 4.x全面迁移至Jakarta EE API,检查项目中所有相关导入(如servlet、jaxws相关类)是否已从javax替换为jakarta,避免类加载冲突。

关键原因说明

  • CXF 4.x的HttpClientHTTPConduit对输出流状态检查更严格,提前关闭流会导致后续传输逻辑中HttpClient实例初始化失败,触发空指针。
  • 原始代码中缓存流的使用逻辑颠倒,未让框架先写入内容再读取修改,直接操作原始流破坏了CXF的流处理链路。

内容的提问来源于stack exchange,提问作者Manav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 19:50:58