It looks like, that beans created by FactoryBeans are passed to ScheduledAnnotationBeanPostProcessor.postProcessAfterInitialization, however never get passed to ScheduledAnnotationBeanPostProcessor.postProcessBeforeDestruction. Only the FactoryBean itself is checked via ScheduledAnnotationBeanPostProcessor.requiresDestruction (and of course fails to require destruction).

As a consequence, the methods in those beans are still continued to be started in parallel during the close-process of the application context - and of course fail when they try to access other beans, entity manager, transaction support, ... They are however finally stopped once the TaskScheduler is shut down.

Normal beans (without FactoryBeans) are created by AbstractAutowireCapableBeanFactory.doCreateBean, which in the end does registerDisposableBeanIfNecessary.

However beans created via FactoryBeans are created by

FactoryBeanRegistrySupport.doGetObjectFromFactoryBean FactoryBeanRegistrySupport,getObjectFromFactoryBean AbstractBeanFactory.getObjectForBeanInstance AbstractBeanFactory.doGetBean

and never get the DisposableBeanAdapter applied. Hence @Scheduled tasks created by those beans are not cancelled.

Please find a stripped down test case at https://github.com/abenneke/sandbox/tree/master/spring-scheduled-factory The ComponentConfiguration creates two ScheduledBeans, one directly and another one via a simple FactoryBean. Both beans have the same testScheduled method to be triggered every 100ms. It also adds a SlowDestroyBean, which simply slows down the application context close process a bit. When you run the Main program, you see that

  • both testScheduled are executed perfectly while the context is up and running,
  • once the closing process is started, the testScheduled invocation of the normal ScheduledBean are stopped as expected, however
  • the testScheduled invocations of the ScheduledBean created via the FactoryBean continue
  • until eventually the TaskScheduler is stopped

This might be related to #14146

Comment From: jhoeller

Along the lines of #27090, ScheduledAnnotationBeanPostProcessor can do early cancelling of all of its tasks at ContextClosedEvent time, through cancel(false) calls which let in-progress tasks complete still, whereas we enforce cancel(true) in the destruction step. This will include tasks from non-tracked beans such as FactoryBean-exposed objects, still cancelled before any other beans get destroyed then.

As a side note: We never really meant to support @Scheduled on FactoryBean-exposed objects to begin with. Like prototype beans, such objects are not being tracked for destruction, just the FactoryBean instance itself. The above addresses graceful shutdown for such scenarios but FactoryBean-exposed objects are nevertheless not fully tracked instances, so it is generally recommendable to avoid @Scheduled on them.