Как я могу завершить работу Spring пулов-исполнителей/планировщиков задач до того, как все остальные beans в веб-приложении будут уничтожены?
В веб-приложении Spring у меня есть несколько DAO и сервисный уровень beans. Один служебный уровень bean имеет аннотированные методы @Async/@Scheduled. Эти методы зависят от других (автоматически) beans.
Я создал два потока в XML:
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="2" />
<property name="maxPoolSize" value="5" />
<property name="queueCapacity" value="5" />
<property name="waitForTasksToCompleteOnShutdown" value="true" />
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy"/>
</property>
</bean>
<bean id="taskScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
<property name="poolSize" value="10" />
<property name="waitForTasksToCompleteOnShutdown" value="true" />
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy"/>
</property>
</bean>
<task:annotation-driven executor="taskExecutor" scheduler="taskScheduler"/>
Все работает так, как ожидалось. Моя проблема в том, что я не могу получить чистое закрытие пулов задач для работы. Задачи работают в базе данных и в файловой системе. Когда я останавливаю веб-приложение, требуется некоторое время, пока он не остановится. Это означает, что свойство waitForTasksToCompleteOnShutdown
работает. Тем не менее, я получаю IllegalStateExceptions в журнале, что указывает на то, что некоторые beans уже уничтожены, но некоторые потоки рабочих задач все еще выполняются, и они терпят неудачу, потому что их зависимости уничтожены.
Существует проблема JIRA, которая может иметь значение: SPR-5387
Мой вопрос: есть ли способ сообщить Spring инициализировать исполнитель/планировщик задач beans last или есть способ сообщить Spring, чтобы сначала их уничтожить?
Я понимаю, что разрушение происходит в обратном порядке init. Поэтому сначала будет уничтожен bean init'ed last. Если пул потоков beans уничтожен первым, все выполняемые в данный момент задачи будут завершены и могут по-прежнему обращаться к зависимым beans.
Я также попытался использовать атрибут зависимости в пулах потоков, ссылаясь на мою службу bean, которая имеет аннотации @Async и @Scheduled. Похоже, что они никогда не исполняются, и я не получаю ошибок инициализации контекста. Я предполагаю, что аннотированная услуга bean каким-то образом сначала инициализируется этими пулами потоков, и если я использую зависящее от меня, я отменяю порядок и делаю их неработоспособными.
Ответы
Ответ 1
Два способа:
-
Имейте bean реализацию ApplicationListener<ContextClosedEvent>
. onApplicationEvent()
будет вызван до контекста, и все beans будут уничтожены.
-
Внесите bean Lifecycle или SmartLifecycle. stop()
будет вызван до контекста, и все beans будут уничтожены.
В любом случае вы можете закрыть материал задачи до того, как будет выполнен механизм уничтожения bean.
Например:
@Component
public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> {
@Autowired ThreadPoolTaskExecutor executor;
@Autowired ThreadPoolTaskScheduler scheduler;
@Override
public void onApplicationEvent(ContextClosedEvent event) {
scheduler.shutdown();
executor.shutdown();
}
}
(Изменить: исправлена подпись метода)
Ответ 2
Я добавил ниже код для завершения задач, которые вы можете использовать. Вы можете изменить числа повторов.
package com.xxx.test.schedulers;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import com.xxx.core.XProvLogger;
@Component
class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> , ApplicationContextAware,BeanPostProcessor{
private ApplicationContext context;
public Logger logger = XProvLogger.getInstance().x;
public void onApplicationEvent(ContextClosedEvent event) {
Map<String, ThreadPoolTaskScheduler> schedulers = context.getBeansOfType(ThreadPoolTaskScheduler.class);
for (ThreadPoolTaskScheduler scheduler : schedulers.values()) {
scheduler.getScheduledExecutor().shutdown();
try {
scheduler.getScheduledExecutor().awaitTermination(20000, TimeUnit.MILLISECONDS);
if(scheduler.getScheduledExecutor().isTerminated() || scheduler.getScheduledExecutor().isShutdown())
logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has stoped");
else{
logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has not stoped normally and will be shut down immediately");
scheduler.getScheduledExecutor().shutdownNow();
logger.info("Scheduler "+scheduler.getThreadNamePrefix() + " has shut down immediately");
}
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Map<String, ThreadPoolTaskExecutor> executers = context.getBeansOfType(ThreadPoolTaskExecutor.class);
for (ThreadPoolTaskExecutor executor: executers.values()) {
int retryCount = 0;
while(executor.getActiveCount()>0 && ++retryCount<51){
try {
logger.info("Executer "+executor.getThreadNamePrefix()+" is still working with active " + executor.getActiveCount()+" work. Retry count is "+retryCount);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(!(retryCount<51))
logger.info("Executer "+executor.getThreadNamePrefix()+" is still working.Since Retry count exceeded max value "+retryCount+", will be killed immediately");
executor.shutdown();
logger.info("Executer "+executor.getThreadNamePrefix()+" with active " + executor.getActiveCount()+" work has killed");
}
}
@Override
public void setApplicationContext(ApplicationContext context)
throws BeansException {
this.context = context;
}
@Override
public Object postProcessAfterInitialization(Object object, String arg1)
throws BeansException {
return object;
}
@Override
public Object postProcessBeforeInitialization(Object object, String arg1)
throws BeansException {
if(object instanceof ThreadPoolTaskScheduler)
((ThreadPoolTaskScheduler)object).setWaitForTasksToCompleteOnShutdown(true);
if(object instanceof ThreadPoolTaskExecutor)
((ThreadPoolTaskExecutor)object).setWaitForTasksToCompleteOnShutdown(true);
return object;
}
}
Ответ 3
У меня были аналогичные проблемы с запуском потоков в Spring bean. Эти потоки не закрывались должным образом после того, как я вызвал метод executor.shutdownNow() в методе @PreDestroy. Поэтому решение для меня состояло в том, чтобы позволить нить finsih с IO уже начаться и начать больше IO, как только @PreDestroy был вызван. И вот метод @PreDestroy. Для моего приложения было приемлемым ожидание 1 секунды.
@PreDestroy
public void beandestroy() {
this.stopThread = true;
if(executorService != null){
try {
// wait 1 second for closing all threads
executorService.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
Здесь я объяснил все проблемы, возникающие при попытке закрыть потоки. http://programtalk.com/java/executorservice-not-shutting-down/
Ответ 4
Если это будет веб-приложение, вы также можете использовать интерфейс ServletContextListener.
public class SLF4JBridgeListener implements ServletContextListener {
@Autowired
ThreadPoolTaskExecutor executor;
@Autowired
ThreadPoolTaskScheduler scheduler;
@Override
public void contextInitialized(ServletContextEvent sce) {
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
scheduler.shutdown();
executor.shutdown();
}
}
Ответ 5
Мы можем добавить свойство "AwaitTerminationSeconds" как для taskExecutor, так и для taskScheduler, как показано ниже,
<property name="awaitTerminationSeconds" value="${taskExecutor .awaitTerminationSeconds}" />
<property name="awaitTerminationSeconds" value="${taskScheduler .awaitTerminationSeconds}" />
Документация для свойства waitForTasksToCompleteOnShutdown говорит, когда выключение называется
"Spring завершение работы контейнера продолжается, пока текущие задачи завершаются. Если вы хотите, чтобы этот исполнитель блокировал и дождался завершения задач до того, как остальная часть контейнера продолжает закрываться - например, чтобы сохранить другие ресурсы что ваши задачи могут потребоваться - установите свойство" awaitTerminationSeconds "вместо или в дополнение к этому свойству."
http://docs.spring.io/ spring/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport. HTML # setWaitForTasksToCompleteOnShutdown-boolean-
Поэтому всегда рекомендуется использовать waitForTasksToCompleteOnShutdown и ждать свойств TerminationSeconds вместе. Значение awaitTerminationSeconds зависит от нашего приложения.