如何在Web应用程序中的所有其他bean被销毁之前closuresSpring任务执行程序/调度程序池?
在Spring Web应用程序中,我有几个DAO和服务层bean。 一个服务层bean注释了@Async / @Scheduled方法。 这些方法依赖于其他(自动布线)的bean。 我已经在XML中configuration了两个线程池:
<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"/>
一切都按预期工作。 我的问题是,我不能得到任务池的干净closures工作。 这些任务在数据库和文件系统上运行。 当我停止Web应用程序时,需要一些时间直到停止。 这表明waitForTasksToCompleteOnShutdown
属性起作用。 但是,我在日志中得到了IllegalStateExceptions,指出一些bean已经被销毁,但是一些worker任务线程仍在执行,并且因为它们的依赖关系被破坏而失败。
有一个JIRA问题可能是相关的: SPR-5387
我的问题是:有没有办法告诉Spring最后初始化任务执行者/调度器bean还是有办法告诉Spring首先销毁它们?
我的理解是销毁发生在逆序的初始顺序。 因此,最后被初始化的bean将首先被销毁。 如果线程池bean被首先销毁,所有当前正在执行的任务将完成,并且仍然可以访问相关的bean。
我也尝试在线程池上使用depends-on属性来引用具有@Async和@Scheduled注释的服务bean。 看起来像他们从来没有执行过,我没有上下文初始化错误。 我假设带注释的服务bean需要首先初始化这些线程池,如果我使用depends-on,我会颠倒这个顺序,使它们不起作用。
两种方式:
-
有一个bean实现
ApplicationListener<ContextClosedEvent>
。onApplicationEvent()
将在上下文之前被调用,并且所有的bean都被销毁。 -
有一个bean实现生命周期或SmartLifecycle 。
stop()
将在上下文之前被调用,并且所有的bean都被销毁。
无论哪种方式,您可以在bean销毁机制发生之前closures任务。
例如:
@Component public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> { @Autowired ThreadPoolTaskExecutor executor; @Autowired ThreadPoolTaskScheduler scheduler; @Override public void onApplicationEvent(ContextClosedEvent event) { scheduler.shutdown(); executor.shutdown(); } }
(编辑:固定方法签名)
我已经添加了下面的代码来终止你可以使用它的任务。 您可以更改重试号码。
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; }
}
我在Spring bean中启动的线程有类似的问题。 在@PreDestroy方法中调用executor.shutdownNow()后,这些线程没有正确closures。 所以我的解决scheme是让线程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(); } } }
这里我已经解释了在尝试closures线程时遇到的所有问题。 http://programtalk.com/java/executorservice-not-shutting-down/
如果它将成为一个基于Web的应用程序,您也可以使用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(); }
}
我们可以为taskExecutor和taskScheduler添加“AwaitTerminationSeconds”属性如下,
<property name="awaitTerminationSeconds" value="${taskExecutor .awaitTerminationSeconds}" /> <property name="awaitTerminationSeconds" value="${taskScheduler .awaitTerminationSeconds}" />
文档“waitForTasksToCompleteOnShutdown”属性说,当closures被调用
“ 在正在执行任务的过程中,Spring的容器closures仍在继续,如果希望执行程序在容器的其余部分继续closures之前阻塞并等待任务的终止,例如为了保留您的任务可能需要的其他资源- ,设置“awaitTerminationSeconds”属性,而不是或除了此属性。 “
所以总是build议一起使用waitForTasksToCompleteOnShutdown和awaitTerminationSeconds属性。 awaitTerminationSeconds的值取决于我们的应用程序。