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
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
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;
}
}
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);
}
}
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();
}
}