NullPointerExceptionjavax.xml.ws.WebServiceException: java.lang.NullPointerException
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:142)
...
Caused by: java.lang.NullPointerException
at org.apache.cxf.transport.http.HTTPConduit.prepare(HTTPConduit.java:483)
at org.apache.cxf.interceptor.MessageSenderInterceptor.handleMessage(MessageSenderInterceptor.java:46)
...
核心發生在 HTTPConduit.prepare() 內部,提示共享狀態競態。
原代碼復用單例 Service/Port:
// 共享實例,多線程並發調用
MessagingChannelServiceImplService service = new MessagingChannelServiceImplService(wsdlUrl);
MessagingChannelService port = service.getMessagingChannelServiceImplPort();
// 業務線程直接調用 port.xxx()
結論:JAX-WS 規範明確規定 Service 和 Port 實例非線程安全。
官方規範答案:No。Port 實例非線程安全,可移植代碼應同步訪問或使用實例池。
CXF 實際情況:大多數簡單用例下是線程安全的,例外場景:
- 使用
((BindingProvider)port).getRequestContext()設置請求上下文(JAX-WS 規範要求每實例一個 RequestContext)。解決:設置thread.local.request.context=true啟用 ThreadLocal 隔離。- 直接操作
Conduit(如 TLS 設置、超時配置),Conduit 為實例級共享。解決:自定義ConduitSelector實現 ThreadLocal 隔離。- 啟用 Session 支持(Cookie 存儲在 Conduit 中),同樣屬共享狀態。
Proxies are currently not thread-safe.
StaxUtils.getXMLOutputFactory() 返回緩存的 XMLOutputFactoryImpl,其 createXMLStreamWriter 存在競態:多線程同時調用可能獲得同一個 XMLStreamWriterImpl 實例。
建議修復:StaxUtils.getXMLOutputFactory() 每次返回新實例,不緩存。
public class CxfClientHolder {
private static final ThreadLocal PORT_HOLDER = new ThreadLocal() {
@Override
protected MessagingChannelService initialValue() {
MessagingChannelServiceImplService service = new MessagingChannelServiceImplService(wsdlUrl);
return service.getMessagingChannelServiceImplPort();
}
};
public static MessagingChannelService getPort() {
return PORT_HOLDER.get();
}
// 可選:線程結束時清理,防止內存洩漏(線程池場景必須)
public static void remove() {
PORT_HOLDER.remove();
}
}
// 業務調用
public void callService() {
MessagingChannelService port = CxfClientHolder.getPort();
try {
port.businessMethod(params);
} finally {
// 若使用線程池,必須在任務結束時 remove()
// CxfClientHolder.remove();
}
}
修改後併發壓測 零異常,長時間運行穩定。
ThreadLocal.remove(),避免內存洩漏與跨任務污染。thread.local.request.context 或每次調用重新 getRequestContext()。HTTPConduit 配置 Client)複用底層連接,而非 Port 實例。