0

what is the correct way of scheduling one-shot task (runnable) programmatically given time from now in springboot? I cannot find that information anywhere. IIUC I'd like to call

org.springframework.scheduling.TaskScheduler#scheduleWithFixedDelay(java.lang.Runnable, java.util.Date, long)

but TaskScheduler cannot be injected (autoconfigured). Same for ScheduledTaskRegistrar. I can start my own quartz, but that's not right. I want to do it correctly and reuse what's already implemented in springboot. Again, I need to do it programmatically, ie. invoke this, one minute from now, thus no annotations. Even if I implement SchedulingConfigurer, ScheduledTaskRegistrar passed into configureTask does not have set TaskScheduler.

This seems to be completely undocumented, while this should be really easy to do. Can someone advice?

EDIT: sorry I missread the documentation, desired method is org.springframework.scheduling.TaskScheduler#schedule(java.lang.Runnable, java.util.Date), as one mentioned above invokes the job multiple times, while just once is desired. But the crux is getting in touch with TaskScheduler in firstplace, so the rest holds. Note: manually configuring ThreadPoolTaskScheduler works, but shouldn't there be one already configured by springboot if @EnableScheduling is present?

Martin Mucha
  • 2,385
  • 1
  • 29
  • 49
  • Have you seen [this](https://www.baeldung.com/spring-task-scheduler) tutorial? – Boris Sep 19 '18 at 10:55
  • you can achieve it by using JAVA Concurrency API, here is an example [here](https://stackoverflow.com/questions/19489781/scheduled-task-using-executorservice) – Mohamed chiheb Ben jemaa Sep 19 '18 at 10:58
  • no (I hate that nowadays there has to be tutorials substituting documentation), thanks for link. Can you explain the relation to @EnableScheduling? Ie. lets say, that you have several methods driven via @Scheduled(...), thus there has to be some TaskScheduler or something in play already. What will happen if I configure competing TaskScheduler, following tutorial you provided? – Martin Mucha Sep 19 '18 at 11:03
  • Why wouldn't you be able to inject a `TaskScheduler`? – M. Deinum Sep 19 '18 at 11:40
  • medbenjemaa: sure, but I'm not convinced that this is the correct spring way. – Martin Mucha Sep 19 '18 at 11:52
  • M. Deinum: no idea. But I'm not. Maybe I'm doing something wrong: `@Configuration @EnableScheduling` is present over one configuration class, and when trying to inject `private final TaskScheduler taskScheduler; ` via constructor injection into @Service, app won't start: 'required a bean of type 'org.springframework.scheduling.TaskScheduler' that could not be found'. Same for @Autowire ...ing into field. – Martin Mucha Sep 19 '18 at 11:57
  • Have you seen [this](https://stackoverflow.com/a/30347649/3301492) answer? – Boris Sep 19 '18 at 14:30

2 Answers2

0

Here is how we can enable task scheduling in Spring.

  1. Create a config class annotated with @EnableScheduling
@Configuration
@EnableScheduling
public class ApplicationConfiguration {
}

@EnableScheduling is a Spring Context module annotation which will automatically register a ScheduledAnnotationBeanPostProcessor bean. It will scan Spring beans for the presence of the @Scheduled annotation.

  1. Create a task class with a method annotated with @Scheduled
@Component
public class MyTask {

  private static final Logger LOGGER = LoggerFactory.getLogger(MyTask.class);

  @Scheduled(cron="*/5 * * * * ?")
  public void scheduledTask() {
    LOGGER.info("Execute task " + LocalDateTime.now());
  }

}

Note that the method must have no arguments and should have a void return type.

  1. And the main application class
@SpringBootApplication
public class SpringSchedulingExample {

  public static void main(String[] args) {
    SpringApplication.run(SpringSchedulingExample.class, args);
  }

}

Start the application and you will see a message being logged every five seconds daily as defined in the cron expression:

2018-09-19 15:09:15.000  INFO 15191 --- [pool-1-thread-1] com.example.demo.MyTask : Execute task 2018-09-19T15:09:15
2018-09-19 15:09:20.000  INFO 15191 --- [pool-1-thread-1] com.example.demo.MyTask : Execute task 2018-09-19T15:09:20
2018-09-19 15:09:25.000  INFO 15191 --- [pool-1-thread-1] com.example.demo.MyTask : Execute task 2018-09-19T15:09:25
2018-09-19 15:09:30.000  INFO 15191 --- [pool-1-thread-1] com.example.demo.MyTask : Execute task 2018-09-19T15:09:30
...

Or, for example, to schedule a task on 19 September at 15:25:00:

@Scheduled(cron="0 25 15 19 9 *")
Boris
  • 22,667
  • 16
  • 50
  • 71
  • 1
    Thanks for this answer, but question stated directly "thus no annotations", and your solution uses @Scheduled. Later I added: "while just once is desired"[regarding number of invocations], while your solution will wxwcute task multiple times... Ie. I know all this, but it does not cover the question. – Martin Mucha Sep 19 '18 at 15:10
0

OK, so I failed to find out, how to reuse existing TaskScheduler/threadpool, so the best I was able to come up with is following configuration, which creates new TaskScheduler bean, and registers it as a new default TaskScheduler. Using this configuration one can autowire/inject TaskScheduler and call methods on it, and @Scheduled methods will use the same ThreadPool.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setTaskScheduler(threadPoolTaskScheduler());
    }

    @Bean
    public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
        ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(1);
        threadPoolTaskScheduler.initialize();
//        threadPoolTaskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler");
        return threadPoolTaskScheduler;
    }
}
Martin Mucha
  • 2,385
  • 1
  • 29
  • 49
  • ...not sure, if it's correct though. Seems to work just fine, however: the sources from I compiled this vary about calling of initialize. Some code calls it, some does not call it... Most relevant documentation is in @EnableScheduling javadoc, which IIUC explains, that if one want to have injectable TaskScheduler, one has to declare it himself. Otherwise "a local single-threaded default scheduler will be created and used within the registrar". But registar is not injectable, and that local pool won't be accessible as bean, thus that TaskScheduler won't be accessible. – Martin Mucha Sep 19 '18 at 15:22