Подтвердить что ты не робот

Как я могу завершить работу 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 каким-то образом сначала инициализируется этими пулами потоков, и если я использую зависящее от меня, я отменяю порядок и делаю их неработоспособными.

4b9b3361

Ответ 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 зависит от нашего приложения.