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 its initialize()
method before returning it:
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(1);
executor.setCorePoolSize(1);
executor.setThreadNamePrefix("CUSTOM-");
// Initialize the executor
executor.initialize();
return executor;
}
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 before getAsyncExecutor()
and getAsyncUncaughtExceptionHandler()
are executed:
AsyncBean.java
:@Component
public class AsyncBean implements IAsyncBean {
@Override
@Async
public void whoAmI() {
final String message =
String.format("My name is %s and I am running in %s", getClass().getSimpleName(), Thread.currentThread());
System.out.println(message);
}
}
AsyncDemoApp.java
:@SpringBootApplication
@EnableAsync
public class AsyncDemoApp implements AsyncConfigurer {
@Autowired
private IAsyncBean asyncBean;
public static void main(String[] args) {
SpringApplication.run(AsyncDemoApp.class, args);
}
@Override
public Executor getAsyncExecutor() {
System.out.println("AsyncDemoApp.getAsyncExecutor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("CUSTOM-");
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
System.out.println("AsyncDemoApp.getAsyncUncaughtExceptionHandler");
return (throwable, method, objects)
-> throwable.printStackTrace();
}
@PostConstruct
public void start() {
System.out.println("AsyncDemoApp.start");
asyncBean.whoAmI();
}
}
AsyncDemoApp.start
My name is AsyncBean and I am running in Thread[main,5,main]
AsyncDemoApp.getAsyncExecutor
AsyncDemoApp.getAsyncUncaughtExceptionHandler
However, if you use your bean after the application context is ready to use, it should all work as expected:
@SpringBootApplication
@EnableAsync
public class AsyncDemoApp implements AsyncConfigurer {
public static void main(String[] args) {
final ConfigurableApplicationContext context = SpringApplication.run(AsyncDemoApp.class, args);
final IAsyncBean asyncBean = context.getBean(IAsyncBean.class);
asyncBean.whoAmI();
}
@Override
public Executor getAsyncExecutor() {
System.out.println("AsyncDemoApp.getAsyncExecutor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("CUSTOM-");
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
System.out.println("AsyncDemoApp.getAsyncUncaughtExceptionHandler");
return (throwable, method, objects)
-> throwable.printStackTrace();
}
}
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
to AsyncBean
and using a CommandLineRunner
to run the app (personally I think this is a bug. The behaviour is very surprising to say the least):
AsyncBean
with @PostConstruct
:@Component
public class AsyncBean implements IAsyncBean {
@Override
@Async
public void whoAmI() {
final String message =
String.format("My name is %s and I am running in %s", getClass().getSimpleName(), Thread.currentThread());
System.out.println(message);
}
@PostConstruct
public void postConstruct() {
System.out.println("AsyncBean is constructed");
}
}
AsyncDemoApp
implementing CommandLineRunner
:@SpringBootApplication
@EnableAsync
public class AsyncDemoApp implements AsyncConfigurer, CommandLineRunner {
@Autowired
private IAsyncBean asyncBean;
public static void main(String[] args) {
SpringApplication.run(AsyncDemoApp.class, args);
}
@Override
public Executor getAsyncExecutor() {
System.out.println("AsyncDemoApp.getAsyncExecutor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("CUSTOM-");
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
System.out.println("AsyncDemoApp.getAsyncUncaughtExceptionHandler");
return (throwable, method, objects)
-> throwable.printStackTrace();
}
@Override
public void run(String... args) throws Exception {
System.out.println("AsyncDemoApp.run");
asyncBean.whoAmI();
}
}
AsyncBean is constructed
AsyncDemoApp.getAsyncExecutor
AsyncDemoApp.getAsyncUncaughtExceptionHandler
AsyncDemoApp.run
My name is AsyncBean and I am running in Thread[main,5,main]
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 of setDaemon(boolean)
says:
Set whether this factory is supposed to create daemon threads, just executing as long as the application itself is running. Default is "false": Concrete factories usually support explicit cancelling. Hence, if the application shuts down, Runnables will by default finish their execution. Specify "true" for eager shutdown of threads which still actively execute a Runnable at the time that the application itself shuts down.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With