3

I want to do something like javascript's setInterval(function, interval)/setTimeout(function, timeout) in Spring Boot.

I found the @Scheduled annotation that has the fixedRate argument, but as an annotation I cannot change the rate dynamically (Or can I?)

For now I am using java.util.Timer, but I would rather use Spring. Is there a way?

Can I get a Scheduler instance and work with it dynamically?

thanks!

Tomer Amir
  • 1,515
  • 4
  • 27
  • 54

3 Answers3

3

You may use a Trigger which lets you dynamically control the next execution. You need to implement SchedulingConfigurer, another answer covers exactly this:

Scheduling a job with Spring programmatically (with fixedRate set dynamically)

EDIT to answer comments:

nextExecutionTime is called on and on and on... The next time the task (and nextExecutionTime) is called is defined by this:

nextExecutionTime.setTime(lastActualExecutionTime != null ? lastActualExecutionTime : new Date());
nextExecutionTime.add(Calendar.MILLISECOND, numberOfMillisecondsBeforeCallingTheTask);

All you need to do is have this numberOfMillisecondsBeforeCallingTheTask value changed.

Example:

@RestController
public class MyController {

    public static int triggerDelay = 1000;

    @RequestMapping("/changetrigger/{val}")
    public void test(@PathVariable int val){
        this.triggerDelay = val;
    }
}
@SpringBootApplication
@EnableScheduling
public class Launcher implements SchedulingConfigurer{

    public static void main(String[] args){
        new SpringApplicationBuilder() //
        .sources(Launcher.class)//
        .run(args);
    }

    @Bean(destroyMethod = "shutdown")
    public Executor taskExecutor() {
        return Executors.newScheduledThreadPool(100);
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(taskExecutor());
         ;
        taskRegistrar.addTriggerTask(new TriggerTask(new Runnable() {
            @Override
            public void run() {
                System.out.println("blah");
                System.out.println(System.currentTimeMillis());
            }
        }, new Trigger() {
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
                Calendar nextExecutionTime = new GregorianCalendar();
                nextExecutionTime.setTime(new Date());
                nextExecutionTime.add(Calendar.MILLISECOND, MyController.triggerDelay);
                System.out.println(System.currentTimeMillis());
                return nextExecutionTime.getTime();
            }}));
    }
}

Notice how the dynamic value MyController.triggerDelay is used for the next execution. So if you change the number, the next execution time will be changed. You'll see if you put a breakpoint inside nextExecutionTime.

Community
  • 1
  • 1
alexbt
  • 16,415
  • 6
  • 78
  • 87
  • Looks good, but it only runs on application startup, doesn't it? I need to start and stop scheduled tasks all the time – Tomer Amir Aug 31 '16 at 12:13
  • I found a solution thanks to you, but it is a bit different. probably more suited to my needs. I voted up, and I will add my own solution to get some feedback about it. Thanks! – Tomer Amir Aug 31 '16 at 14:13
0

You can use @Scheduled(fixedRateString = "${spring.boot.schedule.rate}") for your case, where the spring.boot.schedule.rate is the external properties in application.properties

spring.boot.schedule.rate=5000

Misunderstand the question, above is just the externalize the properties.

For the dynamic solution, maybe this should be work, using the spEL in the annonation:

@Service
public class ScheduledService {
    @Autowired
    private FixRateProperty fixRateProperty;

    @Scheduled(fixedRateString = "#{fixRateProperty.fixRate}")
    private void reportCurrentTime() {
        System.out.println(new Date());;
    }
}

This is the FixRateProperty

@Component
public class FixRateProperty {
    private Long fixRate = 500L;

    public Long getFixRate() {
        return fixRate;
    }

    public void setFixRate(Long fixRate) {
        this.fixRate = fixRate;
    }
}

so you can externalize the rate in the properties or set the fixRate somewhere.

Liping Huang
  • 4,378
  • 4
  • 29
  • 46
  • This is not a dynamic solution. it is determined on application start, and it is a global constant. If I want to run multiple tasks with different schedules it will not work. – Tomer Amir Aug 31 '16 at 13:10
  • @Algosub sorry, I update the answer, hope this will help you. – Liping Huang Aug 31 '16 at 13:22
  • This is still not what I need. Please look at the answer I posted, it might clarify what I need. I don't know if my solution is a good one, but it works for me. You are welcome to comment on it and I am open to suggestions. – Tomer Amir Aug 31 '16 at 14:24
0

Found a solution that works for my case.

In Main.java:

@SpringBootApplication
@ConfigurationProperties
@EnableScheduling
public class Main {
    @Bean
    ThreadPoolTaskScheduler taskScheduler() {
        return new ThreadPoolTaskScheduler();
    }

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

In Service.java (Called from a rest controller):

@Service
public class Service {
    private static final Logger log = LoggerFactory.getLogger(Service.class);
    private final TaskScheduler scheduler;

    @Autowired
    public Service(TaskScheduler scheduler) {
        this.scheduler = scheduler;
    }

    public void startTask(int inteval) {
        scheduler.schedule(() -> log.info("Doing work"), triggerContext -> {
            if (some_condition) {
                ZonedDateTime now = ZonedDateTime.now();

                return Date.from(now.plusSeconds(interval).toInstant());
            } else {
                // Stop the execution
                return null;
            }
        });
    }
}

This solution works, but I'm not sure it is the correct way.

You are welcome to comment below, and I might change the solution if I get a suggestion I find helpful.

Tomer Amir
  • 1,515
  • 4
  • 27
  • 54