Search code examples
javaspringspring-bootjavabeans

Same Data applied across all Beans of same type while creating a list of beans with different data


My use case is to create a List of Beans "B" each having a different instance of a dependent bean "C".

@Component("a")
public class A{

    List<B> bPool = new ArrayList<>();
    private ApplicationContext appContext;

    @Autowired 
    A(ApplicationContext appContext){
        this.appContext = appContext;
    }

    @PostConstruct
    public void init(){
        for(int i=0; i<POOL_SIZE; i++){
            bPool.add((B) appContext.getBean("b"));
        }
    }

    //code for multi-threading, which uses beans from the list bPool. 
    //I iterate the list, launch multiple threads, 
    //pass different data to each thread and combine results.
    List<CompletableFuture> multiThreads = new ArrayList<>();
    Iterator it = bPool.iterator();
    for(Data d : listOfSomeData){ //same size for listOfSomeData and bPool
        CompletableFuture var = CompletableFuture.supplyAsync(() -> {
            B curr = (B) it.next()
            curr.someMethodInB(d);
        });
        multiThreads.add(var);
    }
    multiThreads.forEach(cf -> cf.join());
}

@Component("b")
@Scope("prototype")
public class B{
    //Service class - has some dependencies, like C below
    private C c;
    private ApplicationContext appContext;

    @Autowired    
    B(ApplicationContext appContext){
        this.appContext = appContext;
    }

    @PostConstruct
    public void init(){
        c = (C) appContext.getBean("c");
    }

}

@Component("c")
@Scope("prototype")
public class C{
    //this class holds some data and does some processing on it, 
    //I want this to be different for different instances in different threads.
}

While creating list of Bs in bPool, while constructing (I checked by printing in post-construct), a different instance of C is being set for every B respectively.

However, while using the B instances later from the same pool, All the B instances are having the same C instance.

They all have the C instance which was set to the last created B element of bPool.

I am new to springboot and unable to understand this behavior. Any help appreciated. Thanks


Solution

  • Still unsure about what was causing the issue. However a different approach solved my use case.

    I went ahead and used the Async functionality provided by spring itself.

    Refer: documentation link

    1. In your main class, Add @EnableAsync annotation and create a pool of background threads to run.

      @SpringBootApplication
      @EnableAsync
      public class Application {
      
      public static void main(String[] args) {
          SpringApplication.run(Application.class, args).close();
      }
      
      @Bean
      public Executor asyncExecutor() {
          ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
          executor.setCorePoolSize(2);
          executor.setMaxPoolSize(2);
          executor.setQueueCapacity(500);
          executor.setThreadNamePrefix("ThreadName-");
          executor.initialize();
          return executor;
      }
      }
      
    2. Create a new class with a public method with @Async annotation containing the heavy code that we want to run in background. This method needs to be public and has to be in a different class for proxying to work. We cannot create this async method in the same class from where it will be called.

      @Component
      public class MyAsyncService {
      
          @Async
          public CompletableFuture<ResultClass> myAsyncMethod(String params){
              //do some heavy tasks here
              return CompletableFuture.completedFuture(instanceOfResultClass);
          }
      }
      
      1. Call the above class with async method from a caller class.

        @Component
        public class CallerService{
        
            private MyAsyncService myAsyncService;
        
            @Autowired
            public CallerService(MyAsyncService myAsyncService){
                this.myAsyncService = myAsyncService;
            }
        
            public void myMethod(){
                CompletableFuture<ResultClass> result1 = myService.findUser("PivotalSoftware");
                CompletableFuture<ResultClass> result2 = gitHubLookupService.findUser("CloudFoundry");        
        
                CompletableFuture.allOf(result1, result2).join();
        
                ResultClass finalResult1 = result1.get();
                ResultClass finalResult2 = result2.get();
            }
        }