如何解决‘java.lang.IllegalStateException: No thread-bound request found’异常?
问题描述
我正在开发微服务MS-1,它需要通过REST调用获取另一微服务MS-2的信息。MS-1当前提供公开接口,同时监听第三方微服务MS-3的Kafka消息(后续将删除该接口,仅监听MS-2的Kafka消息)。当通过接口调用MS-1时,它能正常调用MS-2并获取正确返回结果;但当MS-1通过Kafka接收MS-3的消息后,调用MS-2时会抛出java.lang.IllegalStateException: No thread-bound request found异常。
异常信息
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request. at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-6.0.10.jar:6.0.10] at org.springframework.web.context.support.WebApplicationContextUtils.currentRequestAttributes(WebApplicationContextUtils.java:313) ~[spring-web-6.0.10.jar:6.0.10] at org.springframework.web.context.support.WebApplicationContextUtils$SessionObjectFactory.getObject(WebApplicationContextUtils.java:370) ~[spring-web-6.0.10.jar:6.0.10] at org.springframework.web.context.support.WebApplicationContextUtils$SessionObjectFactory.getObject(WebApplicationContextUtils.java:365) ~[spring-web-6.0.10.jar:6.0.10] at org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler.invoke(AutowireUtils.java:283) ~[spring-beans-6.0.10.jar:6.0.10] at jdk.proxy2/jdk.proxy2.$Proxy190.getAttribute(Unknown Source) ~[na:na] at [PROTECTED PATH].getHeaders(RestProxyService.java:65) ~[classes/:na] at [PROTECTED PATH].restExchange(RestProxyService.java:431) ~[classes/:na] at [PROTECTED PATH].callRemoteApi(RestProxyService.java:346) ~[classes/:na] at [PROTECTED PATH].callRestApi(RestProxyService.java:426) ~[classes/:na] at [PROTECTED PATH].getDetail(MyProxy.java:57) ~[classes/:na] at [PROTECTED PATH].entriesToReport(EntityWithDataService.java:75) ~[classes/:na] at [PROTECTED PATH].getReportObjects(SupportedReportCodes.java:69) ~[classes/:na] at [PROTECTED PATH].generate(ReportService.java:72) ~[classes/:na] at [PROTECTED PATH].receive(KafkaConsumer.java:25) ~[classes/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na] at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:169) ~[spring-messaging-6.0.10.jar:6.0.10] at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:119) ~[spring-messaging-6.0.10.jar:6.0.10] at org.springframework.kafka.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:56) ~[spring-kafka-3.0.8.jar:3.0.8] at org.springframework.kafka.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:375) ~[spring-kafka-3.0.8.jar:3.0.8] at org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(RecordMessagingMessageListenerAdapter.java:92) ~[spring-kafka-3.0.8.jar:3.0.8] at org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter.onMessage(RecordMessagingMessageListenerAdapter.java:53) ~[spring-kafka-3.0.8.jar:3.0.8] at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeOnMessage(KafkaMessageListenerContainer.java:2924) ~[spring-kafka-3.0.8.jar:3.0.8] at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeOnMessage(KafkaMessageListenerContainer.java:2904) ~[spring-kafka-3.0.8.jar:3.0.8] at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.lambda$doInvokeRecordListener$58(KafkaMessageListenerContainer.java:2822) ~[spring-kafka-3.0.8.jar:3.0.8] at io.micrometer.observation.Observation.lambda$observe$4(Observation.java:544) ~[micrometer-observation-1.11.1.jar:1.11.1] at io.micrometer.observation.Observation.observeWithContext(Observation.java:603) ~[micrometer-observation-1.11.1.jar:1.11.1] at io.micrometer.observation.Observation.observe(Observation.java:544) ~[micrometer-observation-1.11.1.jar:1.11.1] at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeRecordListener(KafkaMessageListenerContainer.java:2820) ~[spring-kafka-3.0.8.jar:3.0.8] at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeWithRecords(KafkaMessageListenerContainer.java:2672) ~[spring-kafka-3.0.8.jar:3.0.8] at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeRecordListener(KafkaMessageListenerContainer.java:2558) ~[spring-kafka-3.0.8.jar:3.0.8] at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeListener(KafkaMessageListenerContainer.java:2200) ~[spring-kafka-3.0.8.jar:3.0.8] at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeIfHaveRecords(KafkaMessageListenerContainer.java:1555) ~[spring-kafka-3.0.8.jar:3.0.8] at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.pollAndInvoke(KafkaMessageListenerContainer.java:1519) ~[spring-kafka-3.0.8.jar:3.0.8] at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.run(KafkaMessageListenerContainer.java:1394) ~[spring-kafka-3.0.8.jar:3.0.8] at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1804) ~[na:na] at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
相关配置代码
消费者(监听端)-> MS-1
application.properties
kafka.security.enabled=false spring.kafka.bootstrap-servers=[PROTECTED] #spring.kafka.producer.topic=report spring.kafka.consumer.group-id=reports spring.kafka.consumer.topic=report spring.kafka.consumer.auto-offset-reset=earliest spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.ErrorHandlingDeserializer spring.kafka.consumer.properties.spring.deserializer.value.delegate.class=org.springframework.kafka.support.serializer.JsonDeserializer spring.kafka.consumer.properties.spring.json.trusted.packages=* spring.kafka.consumer.properties.spring.json.type.mapping=log:[PROTECTED PATH].ReportMessageObject
KafkaConsumer 类
@Component @Slf4j public class KafkaConsumer { @Autowired private MyService myService; @KafkaListener(topics = "${spring.kafka.consumer.topic}", errorHandler = "kafkaEventErrorHandler", properties = "{spring.json.value.default.type=[PROTECTED PATH].ReportMessageObject}") public void receive(@Payload @Valid ConsumerRecord<String, ReportMessageObject> payload) { try { log.debug("received payload={}", payload.toString()); myService.generate(payload.value().getCompanyId(), payload.value().getBody()); } catch (Exception e) { log.error("Exception occurred while consuming ExternalInterface message {}", e.getMessage(), e); } } }
KafkaConsumerConfig 类
@EnableKafka @Configuration public class KafkaConsumerConfig { @Value(value = "${spring.kafka.bootstrap-servers}") private String kafkaAddress; @Value(value = "${spring.kafka.consumer.group-id: null}") private String kafkaGroup; @Value("${kafka.configuration.security.enabled:false}") private boolean kafkaSecurityEnabled; @Value("${spring.kafka.properties.security.protocol:null}") private String kafkaSecurityProtocol; @Value("${spring.kafka.properties.sasl.mechanism:null}") private String kafkaSecurityMechanism; @Value("${spring.kafka.properties.sasl.jaas.config:null}") private String kafkaSecurityJaasConfig; @Bean public ConsumerFactory<String, ReportMessageObject> responseConsumerFactory() { return new DefaultKafkaConsumerFactory<>(getKafkaConsumerConfigProperties(kafkaGroup), new StringDeserializer(), new JsonDeserializer<>(ReportMessageObject.class)); } @Bean public ConcurrentKafkaListenerContainerFactory<String, ReportMessageObject> responseKafkaListenerFactory() { ConcurrentKafkaListenerContainerFactory<String, ReportMessageObject> factory = new ConcurrentKafkaListenerContainerFactory<>(); factory.setConsumerFactory(responseConsumerFactory()); return factory; } private Map<String, Object> getKafkaConsumerConfigProperties(String groupId) { Map<String, Object> configProps = new HashMap<>(); configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaAddress); if(groupId != null && !groupId.isEmpty()) { configProps.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); } if (kafkaSecurityEnabled) { configProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, kafkaSecurityProtocol); configProps.put(SaslConfigs.SASL_MECHANISM, kafkaSecurityMechanism); configProps.put(SaslConfigs.SASL_JAAS_CONFIG, kafkaSecurityJaasConfig); } return configProps; } }
生产者-> MS-3
application.properties
kafka.security.enabled=false spring.kafka.bootstrap-servers=[PROTECTED PATH] spring.kafka.producer.topic=report
KafkaProducer 类
@Slf4j @Component public class KafkaProducer { @Autowired private KafkaTemplate<String, ReportMessageObject> kafkaTemplate; @Autowired private SmartKafkaHeader smartKafkaHeader; private String property1; private String property2; private String property3; private String property4; public void addHeaders(String property1, String property2, String property3, String property4) { this.property1= property1; this.property2= property2; this.property3= property3; this.property4= property4; } public void send(String topic, ReportMessageObject reportMessageObject) { ProducerRecord<String, ReportMessageObject> message = new ProducerRecord<>(topic, reportMessageObject); smartKafkaHeader.addHeadersToRecord(message, property1, property2, property3, property4); kafkaTemplate.send(message); kafkaTemplate.flush(); log.info("sending payload='{}' to topic='{}'", reportMessageObject, topic); } }
KafkaProducerConfig 类
@EnableKafka @Configuration public class KafkaProducerConfig { @Value(value = "${spring.kafka.bootstrap-servers}") private String kafkaAddress; @Value(value = "${spring.kafka.producer.group-id: null}") private String kafkaGroup; @Value("${kafka.configuration.security.enabled:false}") private boolean kafkaSecurityEnabled; @Value("${spring.kafka.properties.security.protocol:null}") private String kafkaSecurityProtocol; @Value("${spring.kafka.properties.sasl.mechanism:null}") private String kafkaSecurityMechanism; @Value("${spring.kafka.properties.sasl.jaas.config:null}") private String kafkaSecurityJaasConfig; @Bean public ProducerFactory<String, ReportMessageObject> producerFactorySmartMessaging() { return new DefaultKafkaProducerFactory<>(getKafkaProducerConfigProperties(kafkaGroup)); } @Bean public KafkaTemplate<String, ReportMessageObject> kafkaMessaging() { return new KafkaTemplate<>(producerFactorySmartMessaging()); } private Map<String, Object> getKafkaProducerConfigProperties(String groupId){ Map<String, Object> configProps = new HashMap<>(); configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaAddress); if(groupId != null && !groupId.isEmpty()) { configProps.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); } configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); configProps.put(JsonSerializer.TYPE_MAPPINGS, "log:[PROTECTED PATH].ReportMessageObject"); configProps.put(ProducerConfig.ACKS_CONFIG, "all"); if (kafkaSecurityEnabled) { configProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, kafkaSecurityProtocol); configProps.put(SaslConfigs.SASL_MECHANISM, kafkaSecurityMechanism); configProps.put(SaslConfigs.SASL_JAAS_CONFIG, kafkaSecurityJaasConfig); } return configProps; } }
解决方案
问题根源
Kafka消费者线程属于独立的非Web请求线程,没有绑定Spring的请求上下文。从异常栈可以看到,RestProxyService.java的getHeaders方法尝试从当前线程获取请求属性(比如Session或Request中的值),但Kafka消费逻辑运行时不存在Web请求上下文,因此抛出异常。
具体修复方案
方案1:重构RestProxyService,移除对请求上下文的依赖(推荐)
这是最彻底的解决方式。检查getHeaders方法中依赖请求上下文的参数(如token、用户信息等),改为通过以下方式传递:
- 从Kafka消息的Header中携带必要参数,消费时解析后传入
RestProxyService - 全局通用配置直接从配置文件读取,而非请求上下文
- 业务相关参数从Kafka消息的Payload中提取
示例修改:
// 原依赖请求上下文的代码 public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); String token = (String) RequestContextHolder.getRequestAttributes().getAttribute("token", RequestAttributes.SCOPE_REQUEST); headers.set("Authorization", "Bearer " + token); return headers; } // 修改后,参数从外部传入 public HttpHeaders getHeaders(String token) { HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "Bearer " + token); return headers; }
在Kafka消费逻辑中提取参数并传入:
// KafkaConsumer的receive方法内 String token = new String(payload.headers().lastHeader("token").value()); HttpHeaders headers = restProxyService.getHeaders(token);
方案2:手动绑定空请求上下文(临时规避)
如果暂时无法重构代码,可以在Kafka消费逻辑中手动创建并绑定空请求上下文,避免抛出异常。但此方法仅为临时方案,无法解决逻辑上的依赖问题:
@KafkaListener(...) public void receive(...) { // 创建空请求上下文并绑定 RequestAttributes requestAttributes = new ServletRequestAttributes(new MockHttpServletRequest()); RequestContextHolder.setRequestAttributes(requestAttributes); try { myService.generate(...); } finally { // 清理上下文,防止线程污染 RequestContextHolder.resetRequestAttributes(); } }
方案3:排除无效配置
异常提示中的RequestContextListener或RequestContextFilter仅用于Web请求线程的上下文传播,对Kafka消费线程无效,无需配置。
内容的提问来源于stack exchange,提问作者André Raja
相关产品推荐
相关产品推荐

