spring sleuth作为spring cloud生态系统中的分布式追踪解决方案,能够自动为http请求添加追踪id(x-b3-traceid、x-b3-spanid等)以及自定义的baggage字段,并在服务间进行传播。这种自动化的能力极大地简化了分布式系统的可观测性配置。
对于以下几种主流的HTTP客户端,Sleuth提供了开箱即用的集成:
当应用程序使用这些客户端发起对外调用时,Sleuth会自动拦截请求,将当前的追踪上下文(包括Baggage字段)注入到请求头中。例如,在application.yaml中配置remote-fields:
sleuth:
async:
enabled: true
baggage:
remote-fields:
- Caller-Id配置后,通过Feign发起的REST调用,其请求头将包含X-B3-*追踪信息以及自定义的caller-id:
Request headers: {Accept=[application/json; distances], Authorization=[Bearer ...], X-B3-TraceId=[...], X-B3-SpanId=[...], X-B3-Sampled=[1], caller-id=[value]}然而,对于使用JAX-WS(Java API for XML Web Services)框架发起的SOAP调用,即使项目中引入了jaxws-spring等辅助库,Spring Sleuth的默认自动传播机制通常不覆盖这类客户端。这意味着,虽然X-B3-*追踪头可能在某些情况下通过底层HTTP传输层被传递(取决于JAX-WS客户端的具体实现和配置),但自定义的Baggage字段(如Caller-Id)则不会自动注入到SOAP消息头中。
观察到的现象是,SOAP请求头可能仅包含如Authorization等标准HTTP头,而缺少Sleuth期望传播的caller-id字段:
SOAP Headers - {Authorization=[Bearer...]}这种差异源于Sleuth的自动插桩是针对特定的HTTP客户端库进行的,JAX-WS客户端(特别是其SOAP消息处理管道)需要不同的集成方式。
要解决JAX-WS SOAP调用中Baggage字段无法传播的问题,我们需要利用JAX-WS提供的扩展点——Handler(处理器),手动将Sleuth上下文中的Baggage字段提取出来,并注入到SOAP消息头中。
创建一个实现javax.xml.ws.handler.soap.SOAPHandler接口的类。这个处理器将在SOAP消息发送前被调用,允许我们修改消息内容。
import brave.baggage.BaggageField; import javax.xml.namespace.QName; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPHeaderElement; import javax.xml.soap.SOAPMessage; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; import java.util.Collections; import java.util.Set; /** * 自定义SOAP处理器,用于将Spring Sleuth的Baggage字段注入到SOAP消息头中。 */ public class SleuthBaggageSOAPClientHandler implements SOAPHandler{ // 定义要传播的Baggage字段名称 private static final String CALLER_ID_FIELD_NAME = "Caller-Id"; // 定义SOAP头元素的命名空间,根据实际需求调整 private static final String SOAP_HEADER_NAMESPACE_URI = "http://your.service.namespace/headers"; private static final String SOAP_HEADER_PREFIX = "hdr"; // 可选前缀 @Override public Set getHeaders() { // 声明此Handler可能处理或添加的SOAP头QName return Collections.singleton(new QName(SOAP_HEADER_NAMESPACE_URI, CALLER_ID_FIELD_NAME)); } @Override public boolean handleMessage(SOAPMessageContext context) { Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); if (outboundProperty) { // 仅处理出站消息(客户端发出的请求) try { SOAPMessage soapMessage = context.getMessage(); SOAPEnvelope soapEnvelope = soapMessage.getSOAPPart().getEnvelope(); SOAPHeader soapHeader = soapEnvelope.getHeader(); // 如果SOAP头不存在,则创建 if (soapHeader == null) { soapHeader = soapEnvelope.addHeader(); } // 从Sleuth的当前追踪上下文中获取Caller-Id Baggage字段的值 // BaggageField.getByName()会从当前TraceContext中查找字段 String callerIdValue = BaggageField.getByName(CALLER_ID_FIELD_NAME).value(); if (callerIdValue != null && !callerIdValue.isEmpty()) { // 创建SOAP头元素并设置值 QName callerIdQName = new QName(SOAP_HEADER_NAMESPACE_URI, CALLER_ID_FIELD_NAME, SOAP_HEADER_PREFIX); SOAPHeaderElement headerElement = soapHeader.addHeaderElement(callerIdQName); headerElement.setValue(callerIdValue); System.out.println("SleuthBaggageSOAPClientHandler: Added Caller-Id to SOAP header: " + callerIdValue); } else { System.out.println("SleuthBaggageSOAPClientHandler: Caller-Id baggage field is null or empty, not added to SOAP header."); } // 保存对SOAP消息的更改 soapMessage.saveChanges(); } catch (Exception e) { System.err.println("Error in SleuthBaggageSOAPClientHandler while adding baggage: " + e.getMessage()); // 根据需要决定是否抛出异常或继续处理 return false; // 阻止消息继续处理 } } return true; // 允许消息继续处理 } @Override public boolean handleFault(SOAPMessageContext context) { // 错误处理逻辑(可选) return true; } @Override public void close(MessageContext context) { // 清理资源(可选) } }
代码说明:
在创建JAX-WS客户端代理时,需要将自定义的SleuthBaggageSOAPClientHandler添加到其处理器链中。
import javax.xml.ws.BindingProvider;
import javax.xml.ws.handler.Handler;
import java.util.ArrayList;
import java.util.List;
// 假设这是你的JAX-WS服务接口和实现
// import com.example.soap.YourSoapService;
// import com.example.soap.YourSoapPort;
public class SoapClientConfig {
public YourSoapPort createSoapClient() {
// 1. 创建JAX-WS服务和端口实例
YourSoapService service = new YourSoapService(); // 替换为你的服务类
YourSoapPort port = service.getYourSoapPort(); // 替换为你的端口类
// 2. 获取客户端代理的BindingProvider
BindingProvider bindingProvider = (BindingProvider) port;
// 3. 获取当前的Handler链
List handlerChain = bindingProvider.getBinding().getHandlerChain();
if (handlerChain == null) {
handlerChain = new ArrayList<>();
}
// 4. 将自定义的SleuthBaggageSOAPClientHandler添加到
Handler链中
handlerChain.add(new SleuthBaggageSOAPClientHandler());
// 5. 设置更新后的Handler链
bindingProvider.getBinding().setHandlerChain(handlerChain);
// 可选:设置SOAP服务的endpoint地址
// bindingProvider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "http://localhost:8080/your/soap/service");
return port;
}
// 示例使用
public static void main(String[] args) {
SoapClientConfig config = new SoapClientConfig();
YourSoapPort client = config.createSoapClient();
// 现在,当通过client调用SOAP方法时,Sleuth Baggage将自动注入
// client.someSoapOperation("data");
}
} 代码说明:
Spring Sleuth在分布式追踪领域提供了强大的自动上下文传播能力,但其开箱即用的支持主要集中在主流的HTTP客户端(RestTemplate、WebClient、Feign)。对于JAX-WS SOAP客户端,由于其消息处理机制的特殊性,需要开发者通过实现自定义的SOAPHandler来手动获取Sleuth上下文中的Baggage字段,并将其注入到SOAP消息头中。这种手动集成方式确保了分布式追踪上下文在异构客户端技术栈中的完整传递,从而维护了整个调用链的可观测性。