How to restart Spring Scheduler gracefully

Keywords: Programming Spring JDK

Timing task is a common function. In some cases, you need to restart or reset the Scheduler Job, but the official API does not provide a method similar to restart, so how to complete this requirement?

Spring Quartz

Spring Quartz(http://www.quartz-scheduler.org/documentation/ )It is a complete set of Cron Job architecture, which can complete complex Task scheduling requirements, support Task persistence, transactional, and even distributed. If the Scheduler is based on Spring Quartz, it's easy to restart. It's the Task management class Scheduler( https://javadoc.io/doc/org.quartz-scheduler/quartz/latest/index.html )Many methods are provided, such as scheduleJob, unscheduleJob, rescheduleJob, deleteJob, addJob, etc. the combination of these methods can achieve the purpose of restart. Refer to This answer.

Spring Scheduler

Compared with Spring Quartz, Spring Scheduler is simpler. It does not need to introduce extra Quartz packages. It can implement simple task scheduling functions. Its internal implementation is based on JDK's scheduled task thread pool ScheduledExecutorService. The class scheduledtaskregister is responsible for the registration of scheduled tasks, and the class TaskScheduler is responsible for the packaging of JDK's ScheduledExecutorService

Spring has two common ways to create a Schedle: >1. Note * * @ Scheduled >2. Implement the SchedulingConfigurer * * interface

How to implement the SchedulingConfigurer interface

The SchedulingConfigurer interface has only one method for customization of scheduled tasks. Here is a simple example

@Configuration      
@EnableScheduling   //Turn on scheduled tasks
public class DynamicScheduleTask implements SchedulingConfigurer {

    [@Override](https://my.oschina.net/u/1162528)
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        // Manual configuration, adding tasks
        taskRegistrar.addTriggerTask(...);
        taskRegistrar.scheduleCronTask(...);
    }
}

In this way, you can get the task registration class scheduledtasksregister, and restart the task is relatively simple. The scheduledtasksregister provides the getScheduledTasks method to get all the registered task information. The ScheduledTask wraps the Future information of the task. As long as these tasks are convenient, you can call the cancel method one by one to stop the task.

Set<scheduledtask> tasks = taskRegistrar.getScheduledTasks();
for (ScheduledTask task : tasks) {
    task.cancel();
}

Then reset the task through the scheduledtaskregister.

How to annotate @ Scheduled annotation

It is convenient and widely used to configure timed tasks by annotation. Only one annotation needs to be added to the task entry method, such as

@Configuration
@EnableScheduling
public class ScheduleTask {
    // execute every 10 sec
    @Scheduled(cron = "0/10 * * * * ?")
    private void configureTasks() {
        System.out.println("task executing...");
    }
}

This method is easy to use because Spring hides a lot of implementation details. Scheduledconfiguration will create a scheduledanotionbeanpostprocessor. In this BeanPostProcessor, a scheduledtasksregister will be created, and then the task configuration will be completed automatically.

In this scheme, there is a big difficulty to restart: you cannot get scheduledtasksregister:

@Configuration
@Role(2)
public class SchedulingConfiguration {
    @Bean(name = {"org.springframework.context.annotation.internalScheduledAnnotationProcessor"})
    @Role(2)
    public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {
        // Create a BeanPostProcessor based on Annotation configuration
        return new ScheduledAnnotationBeanPostProcessor();
    }
}

public class ScheduledAnnotationBeanPostProcessor implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor, Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware, SmartInitializingSingleton, ApplicationListener<contextrefreshedevent>, DisposableBean {
    private final ScheduledTaskRegistrar registrar;
    // In the default constructor, a new scheduledtaskregister is created
    // However, it is not registered in Spring Context, so it cannot be obtained
    public ScheduledAnnotationBeanPostProcessor() {
        this.registrar = new ScheduledTaskRegistrar();
    }
}

Of course, you can get the scheduledanotionbeanpostprocessor first, and then get the private property registrar through reflection. Then you can do the same scheme as above. This method of comparing hacker is not considered here. How to restart in this case?

Take a look at the source code of the scheduledanotionbeanpostprocessor. This class calls ScheduledTasksRegistrar to register and start the scheduled task when the project is started. When the project is closed, the scheduled task will be closed and destroyed:

// After this class is initialized, it is called.
// This Bean variable is usually a Task class marked with @ Scheduled
public Object postProcessAfterInitialization(Object bean, String beanName) {
    if (!(bean instanceof AopInfrastructureBean) &amp;&amp; !(bean instanceof TaskScheduler) &amp;&amp; !(bean instanceof ScheduledExecutorService)) {
        Class<!--?--> targetClass = AopProxyUtils.ultimateTargetClass(bean);
        // Find the method labeled @ Scheduled
        Map<method, set<scheduled>&gt; annotatedMethods = MethodIntrospector.selectMethods(targetClass, (method) -&gt; {
            Set<scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);
            return !scheduledMethods.isEmpty() ? scheduledMethods : null;
        });
        // Traverse method, configure scheduled task
        annotatedMethods.forEach((method, scheduledMethods) -&gt; {
            scheduledMethods.forEach((scheduled) -&gt; {
                // Where to really configure scheduled tasks
                this.processScheduled(scheduled, method, bean);
            });
        });

        return bean;
    } else {
        return bean;
    }
}

// Before this class is destroyed, it is called.
public void postProcessBeforeDestruction(Object bean, String beanName) {
    Set tasks;
    // Remove scheduled tasks from Collection
    synchronized(this.scheduledTasks) {
        tasks = (Set)this.scheduledTasks.remove(bean);
    }

    // cancel task
    if (tasks != null) {
        Iterator var4 = tasks.iterator();

        while(var4.hasNext()) {
            ScheduledTask task = (ScheduledTask)var4.next();
            task.cancel();
        }
    }
}

Did you find that if you want to restart task, you just need to call these two methods! The following is the specific logic of the implementation

public class SchedulerServiceImpl {

    // Get BeanPostProcessor
    @Autowired
    private ScheduledAnnotationBeanPostProcessor postProcessor;

    public void restartAllTasks() {
        // Get all the tasks (with package)
        Set<scheduledtask> tasks = postProcessor.getScheduledTasks();
        Set<object> rawTasks = new HashSet&lt;&gt;(tasks.size());
        for (ScheduledTask task : tasks) {
            Task t = task.getTask();
            ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) t.getRunnable();
            Object taskObject = runnable.getTarget();
            // Put the object associated with the task into the Set (that is, the class with the @ Scheduled method)
            rawTasks.add(taskObject);
        }

        // Call the postProcessBeforeDestruction() method, remove the task and cancel
        for (Object obj : rawTasks) {
            postProcessor.postProcessBeforeDestruction(obj, "scheduledTasks");
        }

        // Call the postProcessAfterInitialization() method to reschedule the task
        for (Object obj : rawTasks) {
            postProcessor.postProcessAfterInitialization(obj, "scheduledTasks");
        }
    }
}

Unexpectedly, in the most complex case, you only need to call the methods provided by Spring to accomplish your goal. You can see how clever Spring is. </object></scheduledtask></scheduled></method,></contextrefreshedevent></scheduledtask>

Posted by Jasp182 on Wed, 25 Dec 2019 23:52:08 -0800