Home > Software design >  How not to use CompletableFuture as a shared state with EventListener in Spring
How not to use CompletableFuture as a shared state with EventListener in Spring

Time:12-06

I need to asynchronously react to the @EventListener, therefore I've created something like this.

@Service
public class AsyncHandler {

    private CompletableFuture<My> future;

    @Async
    public CompletableFuture<My> getMy() {
        future = new CompletableFuture<>();
        return future;
    }

    @EventListener
    public void processEvent(MyEvent event) {
        future.complete(event.my());
    }
}

The problem here is that AsyncHandler is now stateful. Which is wrong. And I don't want to use database, so is there any other way to make the bean stateless while using @EventListener?

CodePudding user response:

You are right, your singleton has state, which is "not good".

One (possible) solution:

  1. make the/refactor "statfeul part" to a "prototype" (request, session) scope bean.
  2. make your "singleton" abstract!
  3. inject the "stateful part" via "method injection" (we cannot "auto wire" lower(shorter-living) scope beans to higher ones...)

As code (example):

  1. State holder:
    public class MyStateHolder {
       // State:
       private CompletableFuture<My> future;
    
       @Async // ?
       public CompletableFuture<My> getMy() {
         future = new CompletableFuture<>();
         return future;
       }
    }
    
  2. Abstract, (no @Service ..yet, no state!):
    public abstract class AsyncHandler {
      @EventListener
      public void processEvent(MyEvent event) { 
         // !!
         delegate().getMy().complete(event.my());
      }
    
      // and now only (abstract!):
      public abstract MyStateHolder delegate();
    }
    
  3. Wiring :
    @Configuration
    class MyConfig {
    
      @Bean
      @Scope("prototype") // !
      public MyStateHolder stateful() {
        return new MyStateHolder();
      }
    
      @Bean // singleton/service:
      public AsyncHandler asyncHandler() {
        return new AsyncHandler() { // !
          @Override // !
          public MyStateHolder delegate() {
            return stateful();// !;)
          }
        };
      }
    }
    

refs: (most of) https://docs.spring.io/spring-framework/docs/current/reference/html/core.html

Especially: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes-sing-prot-interaction

  • Related