Having a Spring configuration class for async methods as:
@Configuration
@EnableAsync(proxyTargetClass = true)
@EnableScheduling
public class AsyncConfiguration {
@Autowired
private ApplicationContext applicationContext;
@Bean
public ActivityMessageListener activityMessageListener() {
return new ActivityMessageListener();
}
@Bean
public TaskExecutor defaultExecutor()
{
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(10);
threadPoolTaskExecutor.setMaxPoolSize(10);
threadPoolTaskExecutor.setQueueCapacity(Integer.MAX_VALUE);
return threadPoolTaskExecutor;
}
All my @Async
methods works as expected but if I implement AsyncConfigurer
into AsyncConfiguration
in order to catch exceptions implementing getAsyncUncaughtExceptionHandler()
method, my beans are not being proxied so methods @Async
doesn't run in a pool executor.
This is the non-working configuration:
@Configuration
@EnableAsync(proxyTargetClass = true)
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {
@Autowired
private ApplicationContext applicationContext;
@Bean
public ActivityMessageListener activityMessageListener() {
return new ActivityMessageListener();
}
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setCorePoolSize(10);
threadPoolTaskExecutor.setMaxPoolSize(10);
threadPoolTaskExecutor.setQueueCapacity(Integer.MAX_VALUE);
return threadPoolTaskExecutor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
What could be happening?
We are using @Async
like this:
public class ActivityMessageListener extends BaseMessageListener {
public static final String PARAM_USER_ID = "userId";
public static final String PARAM_COMPANY_ID = "companyId";
public static final String PARAM_CREATE_DATE = "createDate";
public static final String PARAM_CLASS_NAME = "className";
public static final String PARAM_CLASS_PK = "classPK";
public static final String PARAM_TYPE = "type";
public static final String PARAM_EXTRA_DATA = "extraData";
public static final String PARAM_RECEIVED_USER_ID = "receiverUserId";
@Override @Async(value = "defaultExecutor")
public Future<String> doReceive(Message message) throws Exception {
String name = Thread.currentThread().getName();
Map<String, Object> parameters = message.getValues();
Long userId = (Long)parameters.get(ActivityMessageListener.PARAM_USER_ID);
Long companyId = (Long)parameters.get(ActivityMessageListener.PARAM_COMPANY_ID);
Date createDate = (Date)parameters.get(ActivityMessageListener.PARAM_CREATE_DATE);
String className = (String)parameters.get(ActivityMessageListener.PARAM_CLASS_NAME);
Long classPK = (Long)parameters.get(ActivityMessageListener.PARAM_CLASS_PK);
Integer type = (Integer)parameters.get(ActivityMessageListener.PARAM_TYPE);
String extraData = (String)parameters.get(ActivityMessageListener.PARAM_EXTRA_DATA);
Long receiverUserId = (Long)parameters.get(ActivityMessageListener.PARAM_RECEIVED_USER_ID);
ActivityLocalServiceUtil.addActivity(userId, companyId, createDate, className, classPK, type, extraData, receiverUserId);
return new AsyncResult<String>(name);
}
}
EDIT: I have filed a bug report (SPR-14630).
I was on the verge of submitting a bug report to Spring's issue tracker, however when I was preparing a small app for reproducing the bug, I found and fixed the problem.
First of all, when using
ThreadPoolTaskExecutor
, you should call itsinitialize()
method before returning it:Also for some reason, if I use a bean in a
@PostConstruct
method defined in the same configuration class, it won't run asynchronously. The reason is that the@PostConstruct
method is executed beforegetAsyncExecutor()
andgetAsyncUncaughtExceptionHandler()
are executed:AsyncBean.java
:AsyncDemoApp.java
:Output:
However, if you use your bean after the application context is ready to use, it should all work as expected:
Another weird behaviour is that if you autowire the async bean in the same configuration class, the auto wiring is happening before the custom async executor is configured so the bean does not run asynchronously and it runs in the main thread. This can be verified by adding a
@PostConstruct
toAsyncBean
and using aCommandLineRunner
to run the app (personally I think this is a bug. The behaviour is very surprising to say the least):AsyncBean
with@PostConstruct
:AsyncDemoApp
implementingCommandLineRunner
:Output:
One more thing! :) If you use
ThreadPoolTaskExecutor
, depending on your requirements, you might want to set its daemon property to true, otherwise your app will keep on running forever (this is not a big problem for Web/Worker apps). Here's what the JavaDoc ofsetDaemon(boolean)
says: