2011-07-06 13 views
37

在Spring Web應用程序中,我有幾個DAO和服務層bean。一個服務層bean註釋了@Async/@Scheduled方法。這些方法依賴於其他(自動佈線)bean。 我已經配置了XML中的兩個線程池:如何在Web應用程序中的所有其他bean被銷燬之前關閉Spring任務執行程序/調度程序池?

<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初始化任務執行/調度豆最後還是有辦法告訴Spring摧毀他們先?

我的理解是破壞發生在相反的初始順序。因此,最後發起的bean將首先被銷燬。如果線程池bean首先被銷燬,那麼所有當前正在執行的任務都將完成,並且仍然可以訪問相關的bean。

我也嘗試在線程池上使用depends-on屬性來引用具有@Async和@Scheduled註釋的我的服務bean。似乎他們從來沒有執行過,我沒有得到上下文初始化錯誤。我假設帶註釋的服務bean需要首先初始化這些線程池,如果我使用depends-on,則會顛倒順序並使它們不起作用。

+0

可能這個崗位幫助?http://technicalmumbojumbo.wordpress.com/ 2011/04/13/spring-framework-an-introduction-part-ii-object-lifecycle-autowiring-internationalizationi18n /我不知道這是一個非常好的問題,我很好奇你如何解決這個問題...... –

+0

謝謝。看到@ericacm的回答和我的評論。 – tvirtualw

+1

我能夠通過簡單地註冊關機方法來解決這個問題在摧毀事件。以編程方式:http://docs.spring.io/spring/docs/3.2.0.RC1_to_3.2.0.RC2/changes/docdiffs_org.springframework.scheduling.annotation.html所以下面這個:如果你設置bean的destroy屬性關閉,事情應該工作 – hellojava

回答

45

兩種方式:

  1. 有一個bean實現ApplicationListener<ContextClosedEvent>onApplicationEvent()將在上下文之前調用,並且所有的bean都被銷燬。

  2. 有豆器LifecycleSmartLifecyclestop()將在上下文之前被調用,並且所有的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(); 
    }  
} 

(編輯:固定的方法簽名)

+0

感謝您的回答。對於我的情況,你能否更具體一些?我應該創建一個擴展ThreadPoolTask​​Executor並實現SmartLifecycle的新類嗎?那麼讓getPhase()返回一個高整數值?我將如何去執行停止方法?只需調用ThreadPoolTask​​Executor的shutdown()方法?我應該如何處理stop方法的Runnable參數?我是否也可以使用@PreDestroy批註而不是SmartLifecycle界面?一個簡短的例子或鏈接到代碼片段將不勝感激。 – tvirtualw

+0

我添加了一個例子。 – sourcedelica

+2

以上作品。看起來事件發生了兩次。當我在方法執行中記錄調試消息時,它會記錄兩次。調用shutdown()方法是不夠的,正如我引用的JIRA問題所示。當游泳池關閉時,豆子仍然會被摧毀。在[ExecutorService](http://download.oracle)的JDK API文檔中使用shutdownAndAwaitTermination示例方法。com/javase/6/docs/api/java/util/concurrent/ExecutorService.html)訣竅。我調用shutdownAndAwaitTermination(scheduler.getScheduledExecutor());而不是調用scheduler.shutdown()。 – tvirtualw

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

}

+0

Line「Map schedulers = context .getBeansOfType(ThreadPoolTask​​Scheduler.class);」拋出NullPointerException – gandra404

+0

這可能是上下文沒有設置,因爲項目中的接線不起作用。你能添加更多的堆棧跟蹤嗎? –

+0

我完全改變了應用程序,因此我無法再重現它。 – gandra404

1

如果將是一個基於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();  

    } 

}

3

我有類似的問題與線程的Spring bean正在啓動。在@PreDestroy方法中調用executor.shutdownNow()後,這些線程沒有正確關閉。所以對我來說,解決方案是讓線程finsih與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/

0

我們可以添加 「AwaitTerminationSeconds」 屬性都taskExecutor的和的TaskScheduler如下,

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

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

的文檔 「waitForTasksToCompleteOnShutdown」 物業說,當關閉被稱爲

Spring的集裝箱關機持續而正在執行的任務正在完成如果您希望此執行程序在容器的其餘部分繼續關閉之前阻止並等待終止任務 - 例如,爲了保留您的任務可能需要的其他資源 - 請設置「awaitTermi nationSeconds「屬性,而不是或除此屬性外。

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

因此,它始終是建議使用waitForTasksToCompleteOnShutdown和awaitTerminationSeconds性質在一起。awaitTerminationSeconds的價值取決於我們的應用程序。

相關問題