1

I'm using a CacheManager in a Spring Boot application with SCOPE_REQUEST scope.

@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CacheManager cacheManager() {
  return new ConcurrentMapCacheManager();
}

I'm also using Kafka for communication between microservices. Actually I'm receiving an event through a Kafka consumer and I get the following error:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.cacheManager': Scope 'request' is not active for the current thread;
...
Caused by: 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?

It's clear that the CacheManager bean is missing on the listener thread. My goal is to have let the Spring Boot/Kafka framework to create the mean for each consumed Kafka events just as it's for the web requests. I have no idea how I could achive that, could someone help me ?

Thank you so much, Have a nice day!

2 Answers2

4

@Gary Russel That's true and false at the same time, meantime I succeed to find a solution, create the below class:

    public class KafkaRequestScopeAttributes implements RequestAttributes {

      private Map<String, Object> requestAttributeMap = new HashMap<>();

      @Override
      public Object getAttribute(String name, int scope) {
        if (scope == RequestAttributes.SCOPE_REQUEST) {
          return this.requestAttributeMap.get(name);
        }
        return null;
      }

      @Override
      public void setAttribute(String name, Object value, int scope) {
        if (scope == RequestAttributes.SCOPE_REQUEST) {
          this.requestAttributeMap.put(name, value);
        }
      }

      @Override
      public void removeAttribute(String name, int scope) {
        if (scope == RequestAttributes.SCOPE_REQUEST) {
          this.requestAttributeMap.remove(name);
        }
      }

      @Override
      public String[] getAttributeNames(int scope) {
        if (scope == RequestAttributes.SCOPE_REQUEST) {
          return this.requestAttributeMap.keySet().toArray(new String[0]);
        }
        return new String[0];
      }

      @Override
      public void registerDestructionCallback(String name, Runnable callback, int scope) {
        // Not Supported
      }

      @Override
      public Object resolveReference(String key) {
        // Not supported
        return null;
      }

      @Override
      public String getSessionId() {
        return null;
      }

      @Override
      public Object getSessionMutex() {
        return null;
      }
    }

then add the following two lines into your KafkaListener method's start and end:

RequestContextHolder.setRequestAttributes(new KafkaRequestScopeAttributes());
RequestContextHolder.resetRequestAttributes();

By doing that you can force to create the REQUEST_SCOPE in a Kafka Listener.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • Sure; I was referring to what the framework supports. You can always write your own code. We have now [added an advice chain to the container](https://github.com/spring-projects/spring-kafka/pull/1571), so you could move the setup code to an advice now (next release). – Gary Russell Aug 27 '20 at 13:31
2

Request Scope is for web applications only; it can't be used with Kafka consumers.

Gary Russell
  • 166,535
  • 14
  • 146
  • 179
  • Hi Garry can you refer for custom implemetnation such as @RequsetScope for kafka consumers ? i want to read some headers and add it to bean bounded to the consumer processing lifecycle , is this possible ? – Elia Rohana Aug 27 '20 at 08:43
  • 1
    You can add a `RecordInterceptor` to the container; the next release (2.5.6) has an [advice chain](https://github.com/spring-projects/spring-kafka/pull/1571). – Gary Russell Aug 27 '20 at 13:33