依賴quartz做一個靠譜的定時任務系統(續)

上文其實提到了一個問題,就是使用SchedulerFactoryBean配置quartz的時候,遇到了waitForJobsToCompleteOnShutdown屬性沒有起作用的問題,後來經過仔細分析,發現其實是因爲暴露了FactoryBean中創建的那個bean,然後spring在關閉上下文時,默默調用了Scheduler的無參shutdown方法,導致quartz bean先自行停止,然後SchedulerFactoryBean在停止時,發現quartz scheduler已經停了,就直接return,導致在進行中的任務被終止。

Springboot停止時日誌:
[extShutdownHook] org.quartz.core.QuartzScheduler          : Scheduler betab_scheduler_$_DESKTOP-FGL8JJB1616333804862 paused.
[extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'taskExecutor'
[extShutdownHook] org.quartz.core.QuartzScheduler          : Scheduler betab_scheduler_$_DESKTOP-FGL8JJB1616333804862 shutting down.
[extShutdownHook] org.quartz.core.QuartzScheduler          : Scheduler betab_scheduler_$_DESKTOP-FGL8JJB1616333804862 paused.
[extShutdownHook] org.quartz.core.QuartzScheduler          : Scheduler betab_scheduler_$_DESKTOP-FGL8JJB1616333804862 shutdown complete.
[extShutdownHook] o.s.s.quartz.SchedulerFactoryBean        : Shutting down Quartz Scheduler
[extShutdownHook] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} closed

注意:idea中如果是非debug模式啓動,要模擬正常的進程退出,需要點擊下圖綠框的按鈕,而不是紅方塊,如果是debug模式,可以點擊紅方塊。因爲正常退出纔會執行上下文銷燬,這點要牢記,也可以用shell來啓動,然後ctrl+c。

看上面日誌會發現在SchedulerFactoryBean執行shutdown前,QuartzScheduler的shutdown已經執行了一次,爲什麼呢?原因是下面的代碼(注意下面的有錯誤,不要這樣寫,copy黨注意):

    //@Bean(destroyMethod = "")
    @Bean
    public Scheduler scheduler(SchedulerFactoryBean schedulerFactoryBean) throws Exception {
        Scheduler scheduler = schedulerFactoryBean.getScheduler();
        scheduler.start();
        LOG.info("scheduler.start()");
        return scheduler;
    }

我們先看一下@Bean註解裏的一段話:

As a convenience to the user, the container will attempt to infer a destroy
method against an object returned from the {@code @Bean} method. For example, given
an {@code @Bean} method returning an Apache Commons DBCP {@code BasicDataSource},
the container will notice the {@code close()} method available on that object and
automatically register it as the {@code destroyMethod}. This 'destroy method
inference' is currently limited to detecting only public, no-arg methods named
'close' or 'shutdown'. The method may be declared at any level of the inheritance
hierarchy and will be detected regardless of the return type of the {@code @Bean}
method (i.e., detection occurs reflectively against the bean instance itself at
creation time).
爲了方便用戶,對於有@Bean註解的方法返回的對象(也就是那個bean),容器會嘗試推斷出一個銷燬方法。
比如DBCP,容器會注意它的close方法並自動註冊。
目前銷燬方法只探測公共的,無參的,名字叫close或shutdown的,繼承的也可以。

之所以寫上面的代碼,就是爲了取到scheduler,在啓動時做一些註冊任務,刪除任務的事,其實有別的方式,上面是個錯誤的示範,因爲它暴露了FactoryBean內部的那個bean。spring就會管理並銷燬它,但其實FactoryBean創建的那個bean,在FactoryBean銷燬時,會一起處理。看下面代碼:SchedulerFactoryBean#destroy()

	/**
	 * Shut down the Quartz scheduler on bean factory shutdown,
	 * stopping all scheduled jobs.
	 */
	@Override
	public void destroy() throws SchedulerException {
		if (this.scheduler != null) {
			logger.info("Shutting down Quartz Scheduler");
			this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown);
		}
	}

因爲SchedulerFactoryBean實現了DisposableBean接口,所以上下文銷燬時會銷燬它。
如果我們需要在啓動時處理Scheduler,其實直接注入到任何一個bean裏,寫一個PostConstruct即可,如下:

@Autowired
private Scheduler scheduler;

@PostConstruct
public void doSomethingWithScheduler() throws Exception {
	//do something
	scheduler.start();
	LOG.info("scheduler.start()");
}

我們不需要使用FactoryBean<T>的實例,只在需要T的地方注入T即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章