Reputation: 1671
I want to run a method in a separate thread. Therefore I tried to use @Async but it does not work. The printed output always refers to the same thread id.
MyApp.java
@SpringBootApplication
@EnableAsync
@EnableConfigurationProperties(ConfigProperties.class)
public class MyApp {
public static void main(String[] args) throws Exception {
SpringApplication.run(MyApp.class, args);
}
@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(1000);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setThreadNamePrefix("Async-");
return executor;
}
}
MyService.java
@Slf4j
@Service
public class MyService implements ISichtServiceBroker {
@Inject
public MyService(Helper helper) {
this.helper = helper;
}
@Transactional
public void handle(Message message) {
System.out.println("Execute method synchronously - " + Thread.currentThread().getName());
handleAsync(message);
}
@Async("threadPoolTaskExecutor")
public void handleAsync(Message message) {
System.out.println("Execute method asynchronously - " + Thread.currentThread().getName());
}
}
Upvotes: 6
Views: 12163
Reputation: 6226
First thing : @Async
annotations do not work if they are invoked by ‘this’ . From official doc :
The method calls on that object reference will be calls on the proxy, and as such the proxy will be able to delegate to all of the interceptors (advice) that are relevant to that particular method call. However, once the call has finally reached the target object, the SimplePojo reference in this case, any method calls that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy
Adding to what @Kavithakaran provided as answer, the issue with your code is that you are trying to use @Async
and @Transactional
. You have an asynchronous method getting triggered from a transactional one in the same class .
When using @Async
annotation the Spring framework will use AOP (Aspect Oriented Programming) on the Spring proxy of the @Component
to wrap calls to this method in a Runnable and schedule this Runnable on a task executor. Similarly, @Transactional
also uses AOP and proxying. In a Transactional method normally without a Async the transaction gets propagated through the call hierarchy from one Spring @Component
to the other.
But like in your case when an @Transactional
method calls an @Async
one this does not happen since the async method is being called from the same class, it uses the same thread. Probably because of the fact that @Async annotations do not work if they are invoked by ‘this’. @Async behaves just like the @Transactional annotation.
Also, In order to make Spring manage the transaction of the @Async
method either the @Component
or the method itself should declare the @Transactional
annotation, this way Spring will manage the transaction even if a method is being executed asynchronous.So , like the previous answer my suggestion would be to extract the async method to a separate component make it transactional and use the aync method on that component class method. Have async method like :
@Slf4j
@Transactional
@Component
public class AsyncClass {
@Async("threadPoolTaskExecutor")
public void handleAsync(Message message) {
System.out.println("Execute method asynchronously - " +
Thread.currentThread().getName());
}
}
Upvotes: 6
Reputation: 46
If you look at the @Async description in the below documentation, it specifies that self-invocation of @Async methods will not work when called from the same class. Looks like you are facing a similar issue.
https://www.baeldung.com/spring-async
Upvotes: 1
Reputation: 8213
Spring achieves desired effect of @Transactional
and @Async
by creating proxies. so when you inject MyService
, you are actually injecting a proxy of MyService
which wraps your original class instance. So if the calls go through this proxy, then those annotations will work and if the method get called directly without the proxy, it won't work.
In your case, It is happening like this ProxyMyService.handle() --> MyService.handle() --> MyService.handleAsync()
while you want ProxyMyService.handle() --> MyService.handle() --> ProxyMyService. handleAsync() -->MyService.handleAsync()
In order to achieve that, you should create two service classes and put one method in class and second method in another
Note
And also when you do that in two different services, even though handleAsync
will be executed transactionally, it won't use the transaction inside the handleAsync method. Because transaction to propagate inside that method, they should be executed on same thread
Upvotes: 7