上文其实提到了一个问题,就是使用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。
依赖quartz做一个靠谱的定时任务系统(续)(图1)

看上面日志会发现在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即可。

{{o.name}}
{{m.name}}