Monthly Archives: June 2018

Scheduling Tasks/Jobs with Spring (Boot)

Static Scheduling

Spring offers the annotation @Scheduled to define a task and its corresponding scheduling, e.g., execute this method every 5 minutes. The annotation saves you a great deal of work: in the background, it creates or looks up a scheduler, creates a task which invokes your method, and passes the task to the scheduler with your scheduling arguments (here: every 5 minutes).

Scheduling Parameters

The annotation @Scheduled allows to specify a fixed delay (in ms), a fixed rate (in ms), or a more flexible cron expression if the first two options are not expressive enough for your use case. The following code snippet shows an implementation of our “every 5 seconds”-example from above:

@Component // or any subtype like @Service
public class AnyComponent {

    private static final Logger log = LoggerFactory.getLogger(AnyComponent.class);

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    @Scheduled(fixedRate = 5000)
    public void reportCurrentTime() { // visibility is irrelevant: even private is possible
        log.info("The time is now {}", dateFormat.format(new Date()));
    }
}
Task Method Requirements

The method, which is annotated with @Scheduled, must fulfill the following requirements:

  • The enclosing class of the method must be a Spring component. That is, the class must be annotated with the annotation @Component or an annotation which includes @Component like @Service, for example.
  • The method must be void.
  • The method must have no parameters.
  • The method may be private.
Read Parameter Values from a Properties File

If you do not want to specify the delay or the rate directly in the code, you can read it from the configuration context. For this purpose, Spring provides the parameters fixedDelayString and fixedRateString. The following example reads the seconds of the delay from the configuration key my.property.fixed.delay.seconds. If the value is not available or invalid, a default value of 60 is used. Since the delay expects a value in milliseconds, 000 is appended to the read value.

// with a default value of 60
@Scheduled(fixedDelayString = "${my.property.fixed.delay.seconds:60}000")

Further examples:

// without a default value
@Scheduled(fixedDelayString = "${my.property.fixed.delay.seconds}000")
// without appending
@Scheduled(fixedDelayString = "${my.property.fixed.delay.milliseconds}")
// hard-coded value as string (not recommended due to missing static type checking)
@Scheduled(fixedDelayString = "7000")

With these *String variants, you can define the delay or, respectively, the rate from aproperties file. Note that the value is read only once at startup time. Thus, this approach is still static. You cannot change the value at runtime. We refer to the Javadoc of @Scheduled for more detailed information on the parameters.

Dynamic Scheduling

If you need to read your scheduling arguments dynamically, that is, at runtime, then you @Scheduled is not sufficient. Instead, your can use Spring’s interface TaskScheduler. Declare a field with this type and annotate it with Spring’s @Autowired annotation. Then, you can pass a dynamically created task to the scheduler and specify whether it should be executed once or repeatable with a fixed delay or at a fixed rate. We refer to the documentation of the TaskScheduler for more information.

Sources

Pitfalls with “Convention over Configuration”

The paradigm Convention over Configuration is a great way to develop software (web) application. I like it…IFF it is implemented well.

Problem

Unfortunately, many frameworks which rely on this paradigm are hard to work with. One example is Spring Boot. Users who are new to Spring Boot must read many pages of documentation to learn the “sensible defaults”. These defaults are often not intutive to new users because the users do not yet know the underlying concepts for which the defaults are ment to be. Hence, the first steps with Spring Boot take time – more time than necessary.

Moreover, the defaults seem to work in a magical way – just by putting some new annotations at a class or a method. The javadoc often does not explain how the annotations work. It’s so easy to add a short text that describes that Spring searches the classpath for annotations.

Furthermore, annotations can also cause incompatibility issues with other annotations and conventions. For example, the annotation @DataJpaTest disables full auto-configuration such that you wonder why auto-wiring is not working anymore. Although the javadoc mentions this issue, it is unnecessarily complicated. I wish that adding annotations only adds behavior.

Finally, you can often do one thing in more than one way. Usually, basic annotations are provided which are used by aggregated annotations to cover recurring use cases. This approach often results in annotated classes which have several basic annotations twice or thrice. Although this approach does not influence the functionality, it is really confusing for (new) users who need to write some piece of code: when should I use which (aggregated) annotations? Users who read the code could ask themselves: why did the author of this code add these annotations more than once?

Proposed Solution

  1. Uncover all conventions, e.g., by a configuration file with default values, such that the conventions are visible for (new) users. In this way, the user can read through the config file and learn what features and conventions are provided by the framework. We follow this approach with our framework Kieker.
  2. Each annotation should explain how it is processed. If you have a bunch of similar annotations, either copy and paste the same text or add a javadoc link to the processing engine where the approach is described once at a central place.
  3. Let annotations only add new behavior. Let them not disable other features.
  4. Only provide one way to do something to reduce confusion and time to read/understand. When the (new) user has several possibilities to do the same thing, it is laborious to understand when to use which approach. If there is no other choice, provide at least recommendations when to use which approach.