如何在销毁Web应用程序中的所有其他bean之前关闭Spring任务执行程序/调度程序池?

tvi*_*alw 43 java spring threadpool

在Spring Web应用程序中,我有几个DAO和服务层bean.一个服务层bean具有带注释的@Async/@Scheduled方法.这些方法依赖于其他(自动装配的)bean.我在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"/>
Run Code Online (Sandbox Code Playgroud)

一切都按预期工作.我的问题是我无法完全关闭任务池.任务在数据库和文件系统上运行.当我停止Web应用程序时,它需要一些时间才能停止.这表明该waitForTasksToCompleteOnShutdown物业有效.但是,我在日志中得到IllegalStateExceptions,表明某些bean已经被销毁但是一些工作任务线程仍在执行,并且它们因为依赖项被破坏而失败.

有一个JIRA问题可能是相关的:SPR-5387

我的问题是:有没有办法告诉Spring最后初始化任务执行程序/调度程序bean还是有办法告诉Spring先破坏它们吗?

我的理解是,销毁是以颠倒的初始顺序进行的.因此,最后初始化的bean将首先被销毁.如果首先销毁线程池bean,则所有当前正在执行的任务都将完成,并且仍然可以访问依赖bean.

我还尝试在引用我的服务bean的线程池上使用depends-on属性,该服务bean具有@Async和@Scheduled注释.好像它们从未执行过,我没有得到上下文初始化错误.我假设带注释的服务bean在某种程度上需要首先初始化这些线程池,如果我使用依赖,我会颠倒顺序并使它们不起作用.

sou*_*ica 54

两种方式:

  1. 有一个bean工具ApplicationListener<ContextClosedEvent>. onApplicationEvent()将在上下文之前调用并且所有bean都被销毁.

  2. 有一个bean实现生命周期SmartLifecycle. stop()将在上下文之前调用并且所有bean都被销毁.

无论哪种方式,您都可以在bean销毁机制发生之前关闭任务.

例如:

@Component
public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> {
    @Autowired ThreadPoolTaskExecutor executor;
    @Autowired ThreadPoolTaskScheduler scheduler;

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        scheduler.shutdown();
        executor.shutdown();
    }       
}
Run Code Online (Sandbox Code Playgroud)

(编辑:固定方法签名)

  • 您还可以在bean本身上指定"destroy-method".对于ThreadPoolExecutor而言,没有那么好的方法可以调用,因为shutdown()和shutdownNow()不保证池在返回之前完全终止.但是,您可以为此目的扩展ThreadPoolExecutor类.但我认为你使用ApplicationListener会比上面好多了.但是,您也应该调用executor.awaitTermination,以确保在退出onApplicationEvent方法之前终止池. (6认同)
  • 以上工作.事件似乎发生了两次.当我在方法执行时记录调试消息时,它会被记录两次.调用shutdown()方法是不够的,如我引用的JIRA问题所示.游泳池关闭时,豆类仍然会被摧毁.在[ExecutorService]的JDK API文档中使用shutdownAndAwaitTermination示例方法(http://download.oracle.com/javase/6/docs/api/java/util/concurrent/ExecutorService.html)就可以了.我不是只调用scheduler.shutdown()而是调用shutdownAndAwaitTermination(scheduler.getScheduledExecutor()); (2认同)

fat*_*kin 7

我已添加以下代码来终止您可以使用它的任务.您可以更改重试次数.

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;
}
Run Code Online (Sandbox Code Playgroud)

}