简体   繁体   English

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

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

In a Spring web application I have several DAO and service layer beans.在 Spring web 应用程序中,我有几个 DAO 和服务层 bean。 One service layer bean has annotated @Async / @Scheduled methods.一个服务层 bean 已注释 @Async / @Scheduled 方法。 These methods depend on other (autowired) beans.这些方法依赖于其他(自动装配的)bean。 I have configured two thread pools in XML:我在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"/>

Everything works as expected.一切都按预期工作。 My problem is that I cannot get a clean shutdown of the task pools to work.我的问题是我无法彻底关闭任务池以正常工作。 The tasks operate on the database and on the file system.这些任务在数据库和文件系统上运行。 When I stop the web application it takes some time until it is stopped.当我停止 web 应用程序时,它需要一些时间才能停止。 This indicates that the waitForTasksToCompleteOnShutdown property works.这表明waitForTasksToCompleteOnShutdown属性有效。 However, I get IllegalStateExceptions in the log indicating that some beans are already destroyed but some worker task threads are still executing and they fail because their dependencies are destroyed.但是,我在日志中得到 IllegalStateExceptions ,表明一些 bean 已经被销毁,但一些工作任务线程仍在执行,它们失败,因为它们的依赖关系被破坏了。

There is a JIRA issue which might be relevant: SPR-5387有一个可能相关的 JIRA 问题: SPR-5387

My question is: Is there a way to tell Spring to initialize the task executor/scheduler beans last or is there a way to tell Spring to destroy them first?我的问题是:有没有办法告诉 Spring 最后初始化任务执行程序/调度程序 bean,或者有没有办法告诉 Spring 先销毁它们?

My understanding is that destruction takes place in reversed init order.我的理解是破坏以相反的初始化顺序发生。 Therefore the bean init'ed last will be destroyed first.因此,最后初始化的 bean 将首先被销毁。 If the thread pool beans are destroyed first, all currently executing tasks would finish and could still access dependent beans.如果线程池 bean 首先被销毁,所有当前正在执行的任务都将完成并且仍然可以访问依赖的 bean。

I have also tried using the depends-on attribute on the thread pools referring to my service bean which has the @Async and @Scheduled annotations.我还尝试在引用我的服务 bean 的线程池上使用依赖属性,该服务 bean 具有 @Async 和 @Scheduled 注释。 Seems like they are never executed then and I do not get context initialization errors.似乎它们从未执行过,并且我没有收到上下文初始化错误。 I assume the annotated service bean somehow needs these thread pools initialized first and if I use depends-on I reverse the order and make them non-functional.我假设带注释的服务 bean 不知何故需要首先初始化这些线程池,如果我使用依赖,我会颠倒顺序并使它们不起作用。

Two ways:两种方式:

  1. Have a bean implement ApplicationListener<ContextClosedEvent> .有一个 bean 实现ApplicationListener<ContextClosedEvent> onApplicationEvent() will get called before the context and all the beans are destroyed. onApplicationEvent()将在上下文和所有 bean 被销毁之前被调用。

  2. Have a bean implement Lifecycle or SmartLifecycle .有一个 bean 实现LifecycleSmartLifecycle stop() will get called before the context and all the beans are destroyed. stop()将在上下文之前被调用,并且所有 bean 都被销毁。

Either way you can shut down the task stuff before the bean destroying mechanism takes place.无论哪种方式,您都可以在 bean 销毁机制发生之前关闭任务内容。

Eg:例如:

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

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
        scheduler.shutdown();
        executor.shutdown();
    }       
}

(Edit: Fixed method signature) (编辑:固定方法签名)

I have added below code to terminate tasks you can use it.我添加了下面的代码来终止您可以使用它的任务。 You may change the retry numbers.您可以更改重试次数。

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;
}

} }

I had similar issues with the threads being started in Spring bean.我在 Spring bean 中启动的线程有类似的问题。 These threads were not closing properly after i called executor.shutdownNow() in @PreDestroy method.在我在 @PreDestroy 方法中调用 executor.shutdownNow() 后,这些线程没有正确关闭。 So the solution for me was to let the thread finsih with IO already started and start no more IO, once @PreDestroy was called.所以对我来说,解决方案是让 IO 的线程已经启动,并且在调用 @PreDestroy 后不再启动 IO。 And here is the @PreDestroy method.这是@PreDestroy 方法。 For my application the wait for 1 second was acceptable.对于我的应用程序,等待 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();
            }
        }
    }

Here I have explained all the issues faced while trying to close threads.在这里,我解释了尝试关闭线程时面临的所有问题。 http://programtalk.com/java/executorservice-not-shutting-down/ http://programtalk.com/java/executorservice-not-shutting-down/

If it is going to be a web based application, you can also use the ServletContextListener interface.如果它是一个基于 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();     

    }

} }

We can add "AwaitTerminationSeconds" property for both taskExecutor and taskScheduler as below,我们可以为 taskExecutor 和 taskScheduler 添加“AwaitTerminationSeconds”属性,如下所示,

<property name="awaitTerminationSeconds" value="${taskExecutor .awaitTerminationSeconds}" />

<property name="awaitTerminationSeconds" value="${taskScheduler .awaitTerminationSeconds}" />

Documentation for "waitForTasksToCompleteOnShutdown" property says, when shutdown is called “waitForTasksToCompleteOnShutdown”属性的文档说,当调用关闭时

" Spring's container shutdown continues while ongoing tasks are being completed. If you want this executor to block and wait for the termination of tasks before the rest of the container continues to shut down - eg in order to keep up other resources that your tasks may need -, set the "awaitTerminationSeconds" property instead of or in addition to this property. " " Spring 的容器关闭在正在进行的任务完成时继续。如果您希望此执行程序在容器的 rest 继续关闭之前阻止并等待任务终止 - 例如,为了保持您的任务可能需要的其他资源-,设置“awaitTerminationSeconds”属性来代替或附加此属性。

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

So it is always advised to use waitForTasksToCompleteOnShutdown and awaitTerminationSeconds properties together.因此,始终建议同时使用 waitForTasksToCompleteOnShutdown 和 awaitTerminationSeconds 属性。 Value of awaitTerminationSeconds depends on our application. awaitTerminationSeconds 的值取决于我们的应用程序。

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

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