Spring async не работает, когда реализует AsyncConfigurer
Наличие класса конфигурации Spring для асинхронных методов:
@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;
}
Все мое @Async
методы работают как положено, но если я реализую AsyncConfigurer
в AsyncConfiguration
чтобы поймать реализацию исключений getAsyncUncaughtExceptionHandler()
метод, мои бины не проксируются, поэтому методы @Async
не работает в пуле исполнителя.
Это нерабочая конфигурация:
@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();
}
Что может происходить?
Мы используем @Async
как это:
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);
}
}
1 ответ
РЕДАКТИРОВАТЬ: Я подал отчет об ошибке (SPR-14630).
Я был на грани отправки отчета об ошибке в средство отслеживания проблем Spring, однако, когда я готовил небольшое приложение для воспроизведения ошибки, я нашел и исправил проблему.
Прежде всего, при использовании ThreadPoolTaskExecutor
позвони ее initialize()
метод перед возвратом:
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(1);
executor.setCorePoolSize(1);
executor.setThreadNamePrefix("CUSTOM-");
// Initialize the executor
executor.initialize();
return executor;
}
Также по какой-то причине, если я использую бин в @PostConstruct
метод, определенный в том же классе конфигурации, он не будет работать асинхронно. Причина в том, что @PostConstruct
метод выполняется раньше getAsyncExecutor()
а также getAsyncUncaughtExceptionHandler()
выполнены:
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
Однако, если вы используете ваш bean-компонент после того, как контекст приложения готов к использованию, все должно работать как положено:
@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();
}
}
Другое странное поведение заключается в том, что если вы автоматически подключаете асинхронный компонент в том же классе конфигурации, автоматическое подключение происходит до того, как настраивается пользовательский асинхронный исполнитель, поэтому компонент не запускается асинхронно и выполняется в основном потоке. Это можно проверить, добавив @PostConstruct
в AsyncBean
и используя CommandLineRunner
запустить приложение (лично я думаю, что это ошибка. Поведение очень удивительно, если не сказать больше):
AsyncBean
с @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
реализации 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]
Еще кое-что!:) Если вы используете ThreadPoolTaskExecutor
в зависимости от ваших требований вы можете установить для его свойства daemon значение true, иначе ваше приложение будет работать вечно (это не является большой проблемой для приложений Web/Worker). Вот что такое JavaDoc setDaemon(boolean)
говорит:
Укажите, должна ли эта фабрика создавать потоки демонов, просто выполняя их в течение всего времени работы приложения. По умолчанию установлено значение "false": бетонные заводы обычно поддерживают явную отмену. Следовательно, если приложение закрывается, Runnables по умолчанию завершит свое выполнение. Укажите "true" для активного отключения потоков, которые все еще активно выполняют Runnable во время закрытия самого приложения.