繁体   English   中英

如何在 web 应用程序中的所有其他 bean 被破坏之前关闭 Spring 任务执行器/调度程序池?

[英]How can I shutdown Spring task executor/scheduler pools before all other beans in the web app are destroyed?

在 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"/>

一切都按预期工作。 我的问题是我无法彻底关闭任务池以正常工作。 这些任务在数据库和文件系统上运行。 当我停止 web 应用程序时,它需要一些时间才能停止。 这表明waitForTasksToCompleteOnShutdown属性有效。 但是,我在日志中得到 IllegalStateExceptions ,表明一些 bean 已经被销毁,但一些工作任务线程仍在执行,它们失败,因为它们的依赖关系被破坏了。

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

我的问题是:有没有办法告诉 Spring 最后初始化任务执行程序/调度程序 bean,或者有没有办法告诉 Spring 先销毁它们?

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

我还尝试在引用我的服务 bean 的线程池上使用依赖属性,该服务 bean 具有 @Async 和 @Scheduled 注释。 似乎它们从未执行过,并且我没有收到上下文初始化错误。 我假设带注释的服务 bean 不知何故需要首先初始化这些线程池,如果我使用依赖,我会颠倒顺序并使它们不起作用。

两种方式:

  1. 有一个 bean 实现ApplicationListener<ContextClosedEvent> onApplicationEvent()将在上下文和所有 bean 被销毁之前被调用。

  2. 有一个 bean 实现LifecycleSmartLifecycle 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();
    }       
}

(编辑:固定方法签名)

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

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() 后,这些线程没有正确关闭。 所以对我来说,解决方案是让 IO 的线程已经启动,并且在调用 @PreDestroy 后不再启动 IO。 这是@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/

如果它是一个基于 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”属性的文档说,当调用关闭时

" Spring 的容器关闭在正在进行的任务完成时继续。如果您希望此执行程序在容器的 rest 继续关闭之前阻止并等待任务终止 - 例如,为了保持您的任务可能需要的其他资源-,设置“awaitTerminationSeconds”属性来代替或附加此属性。

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.html#setWaitForTasksToCompleteOnShutdown-boolean-

因此,始终建议同时使用 waitForTasksToCompleteOnShutdown 和 awaitTerminationSeconds 属性。 awaitTerminationSeconds 的值取决于我们的应用程序。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM