我有一项服务,可以调用许多其他服务。这是在控制器类中使用@StreamListener
从Kafka主题读取的。出于可追溯性的目的,还必须将来自Kafka消息的相同标头(原始请求ID)转发给所有其他服务
传统上,使用@PostMapping("/path")
或GetMapping
会生成一个请求上下文,并且可以使用RequestContextHolder.currentRequestAttributes()
从任何地方访问标头,而我只会传递HttpHeaders
每当我需要拨打外部电话时,都会将对象变成RequestEntity
但是在StreamListener
中,不会生成任何请求上下文,并且尝试访问RequestContextHolder
会导致异常
这是我尝试执行的操作的一个示例,导致出现异常:
public class Controller {
@Autowired Service1 service1
@Autowired Service2 service2
@StreamListener("stream")
public void processMessage(Model model) {
service1.execute(model);
service2.execute(model);
}
}
public class Service {
RestTemplate restTemplate;
public void execute(Model model){
// Do some stuff
HttpHeaders httpHeaders = RequestContextHolder.currentRequestAttributes().someCodeToGetHttpHeaders();
HttpEntity<Model> request = new HttpEntity(model, httpHeaders);
restTemplate.exchange(url, HttpMethod.POST, request, String.class);
}
}
我当前的解决方法是将StreamListener
更改为PostMapping
,并使用另一个PostMapping
进行调用,以便可以生成请求上下文。另一种选择是使用ThreadLocal
,但看起来却很简陋
我知道使用@Headers MessageHeaders
批注来访问流标头,但是,如果不将标头传递给每个服务,就很难访问它,并且会影响许多单元测试。
理想情况下,我需要一种方法来创建自己的请求上下文(或任何适当的术语),以放置一个存储请求范围对象(HttpHeader
)的位置,或者通过另一种线程安全的方式来传递请求标头向下堆栈,而不向service.execute
答案 0 :(得分:0)
我已经找到了解决方案,并将其留给其他试图实现类似目标的人
如果您的目标是通过REST控制器和Stream侦听器端对端转发一堆标头,则可能要考虑使用Spring Cloud Sleuth
通过maven或gradle配置将其添加到您的项目中:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
具体地说,在Spring Cloud Sleuth中,有一项功能可以通过在application.properties中设置属性spring.sleuth.propagation-keys
来转发标题或“行李”。这些键值对会在整个跟踪过程中保持不变,包括任何下游http或stream调用,这些调用也实现了相同的传播键。
如果需要在代码级别访问这些字段,则可以使用ExtraFieldPropagation
静态函数来获取和设置它们:
ExtraFieldPropagation.set("country-code", "FO"); // Set
String countryCode = ExtraFieldPropagation.get("country-code"); // Get
请注意,ExtraFieldPropagation
设置程序无法设置已定义的spring.sleuth.propagation-keys
中不存在的属性,因此不会接受任意键
您可以在documentation上阅读以获取更多信息