上文其实提到了一个问题,就是使用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即可。