About Petri Kainulainen

Petri is passionate about software development and continuous improvement. He is specialized in software development with the Spring Framework and is the author of Spring Data book.

Spring from the Trenches: Using Environment Specific Cron Expressions with the @Scheduled Annotation

The @Scheduled annotation offers an easy way to create scheduled tasks in Spring powered applications. We can use it to schedule our tasks by using either periodic scheduling or cron expressions.

Although period scheduling can also be useful, the cron expressions give us much more control over the invocation of the scheduled tasks. That is why they are very useful in real life applications. However, using cron expressions has one major drawback if it is not done right. Let’s find out what that is.

Creating a Scheduled Task

Let’s assume that we want to create a task which is invoked once in a second and which simply writes a message to the log. We can create this task by following these steps (We will skip the required configuration since it is described in the second part of this post):

  1. Create a class called ScheduledJob.
  2. Annotate the class with the @Component annotation.
  3. Create a private Logger field and instantiate the created field.
  4. Create a public method called run() and ensure that its return type is void.
  5. Annotate the method with the @Scheduled annotation and set the used cron expression as the value of the cron attribute (Cron Scheduler in Spring provides a nice overview about cron expressions).
  6. Implement the method by writing a single message to the log.

The source code of the ScheduledJob class looks as follows:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledJob {

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

    @Scheduled(cron = "0-59 * * * * *")
    public void run() {
        LOGGER.debug("run()");
    }
}

The problem of our implementation is that the cron expression is hardcoded. This means that it is not possible to use different configurations in different environments.

If we want to use different scheduling configuration in different environments, we have to change the configuration manually before we create the deployed binary.

This is naturally error prone. Since the consequences of using wrong scheduling configuration can be severe, we have to find a way to move our cron expressions from the code to the configuration files of our project.

Moving Cron Expressions to a Properties File

When I was looking for a solution to our problem, I ran into this thread. The solution described in this blog post is based on that discussion.

The requirements of our solution are following:

  • It must have different configurations for production and development environment.
  • When the scheduled task is run in the development environment, it must be invoked once in a second.
  • When the scheduled task is run in the production environment, it must be invoked once in a minute.

We can fulfill these requirements by following these steps:

  1. Configure Maven.
  2. Create the properties files.
  3. Configure the application context.
  4. Modify the task class.

Let’s get started.

Configuring Maven

We can configure Maven by following these steps:

  1. Create profiles for both development and production environment.
  2. Configure resource filtering.

Let’s move on and find out how this is done.

Creating Profiles for Development and Production Environment

As we remember, we have to create Maven profiles for both development and production environment.

We can create the profile used in the development environment by following these steps:

  1. Add a new profile to the profiles section of the POM file.
  2. Set the id of the created profile to ‘dev’.
  3. Ensure that the development profile is active by default.
  4. Create a property called build.profile.id and set its value to ‘dev’.

We can create the production profile by following these steps:

  1. Add a new profile to the profiles section of the POM file.
  2. Set the id of the created profile to ‘prod’.
  3. Create a property called build.profile.id and set its value to ‘prod’.

The profiles section of our pom.xml file looks as follows:

<profiles>
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <build.profile.id>dev</build.profile.id>
        </properties>
    </profile>
    <profile>
        <id>prod</id>
        <properties>
            <build.profile.id>prod</build.profile.id>
        </properties>
    </profile>
</profiles>

We will use the build.profile.id property when we are configuring the resource filtering of our build. Let’s see how this is done.

Configuring the Resource Filtering

We can configure the resource filtering by following these steps:

  1. Configure the location of the configuration file which contains the profile specific properties (The value of the build.profile.id property identifies the used profile).
  2. Configure the location of the resource directory and activate the resource filtering.

The relevant part of our pom.xml file looks as follows:

<filters>
    <filter>profiles/${build.profile.id}/config.properties</filter>
</filters>
<resources>
    <resource>
        <filtering>true</filtering>
        <directory>src/main/resources</directory>
    </resource>
</resources>

Creating the Properties Files

We can create the required properties files by following these steps:

  1. We have to create a properties file for the development environment.
  2. We have to create a properties file for the production environment.
  3. We have to create a properties file which is read by our application.

Let’s get started.

Creating the Properties File for the Development Environment

We can create the properties file for the development environment by following these steps:

  1. Create a file called config.properties to the profiles/dev directory.
  2. Set the value of the scheduling.job.cron property to ’0-59 * * * * *’. This ensures that the task is invoked once in a second.

The content of the profiles/dev/config.properties file looks as follows:

scheduling.job.cron=0-59 * * * * *

Creating the Properties File for the Production Environment

We can create the properties file for the production environment by following these steps:

  1. Create a file called config.properties to the profiles/prod directory.
  2. Set the value of the scheduling.job.cron property to ’0 0-59 * * * *’. This ensures that the task is invoked once in a minute.

The content of the profiles/prod/config.properties file looks as follows:

scheduling.job.cron=0 0-59 * * * *

Creating the Properties File of Our Application

We can create the properties file of our application by following these steps:

  1. Create a file called application.properties to the src/main/resources directory.
  2. Set the value of the scheduling.job.cron property to ‘${scheduling.job.cron}’. This ensures that the placeholder is replaced with the correct cron expression.

The content of the src/main/resources/application.properties file looks as follows:

scheduling.job.cron=${scheduling.job.cron}

Configuring the Application Context

We can configure the application context of our application by using either a Java configuration class or an XML configuration file.

Both of these options are described in the following.

Java Configuration

We can create the application context configuration class by following these steps:

  1. Create a class called ExampleApplicationContext.
  2. Annotate the class with the @Configuration annotation.
  3. Enable scheduling by annotating the class with the @EnableScheduling annotation.
  4. Annotate the class with the @ComponentScan annotation and configure the scanned packages.
  5. Annotate the class with the @PropertySource annotation and ensure that the properties are loaded from a properties file called application.properties which is found from the classpath.
  6. Create a new PropertySourcesPlaceHolderConfigurer bean.

The source code of our application context configuration class looks as follows:

import org.springframework.context.annotation.*;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.EnableScheduling;

@Configuration
@EnableScheduling
@ComponentScan(basePackages = {
        "net.petrikainulainen.spring.trenches.scheduling"
})
@PropertySource("classpath:application.properties")
public class ExampleApplicationContext {

    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer properties = new PropertySourcesPlaceholderConfigurer();

        properties.setLocation(new ClassPathResource( "application.properties" ));
        properties.setIgnoreResourceNotFound(false);

        return properties;
    }
}

XML Configuration

We can create the application context configuration file by following these steps:

  1. Use the property-placeholder element of the context namespace for loading the properties from the properties file called application.properties which is found from the classpath.
  2. Use the annotation-config element of the context namespace for ensuring that the “general” annotations are detected from our bean classes.
  3. Use the component-scan element of the context namespace for configuring the scanned packages.
  4. Enable scheduling by using the annotation-driven element of the task namespace.

The source code of our application context configuration file looks as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:task="http://www.springframework.org/schema/task"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
      http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd">

    <context:property-placeholder location="classpath:application.properties" ignore-resource-not-found="false"/>

    <context:annotation-config/>
    <context:component-scan base-package="net.petrikainulainen.spring.trenches.scheduling"/>
    <task:annotation-driven/>
</beans>

Modifying the Scheduled Task

Our last step is to modify our task class and ensure that the used cron expression is read from the application.properties file. We can do this by setting the value of the cron attribute of the @Scheduled annotation to ‘${scheduling.job.cron}’.

The source code of the ScheduledJob class looks as follows:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class ScheduledJob {

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

    @Scheduled(cron = "${scheduling.job.cron}")
    public void run() {
        LOGGER.debug("run()");
    }
}

Summary

We have now created a scheduled task which reads the used cron expression from a properties file. This blog post has taught us three things:

  • We learned that hard coding the used cron expression makes it hard to use different configuration in different environments.
  • We learned how we can use Maven for separating the profile specific configuration properties into profile specific configuration files.
  • We learned to configure the application context of our application and read the used cron expression from a properties file.

As always, the example application of this blog post is available at Github.
 

Related Whitepaper:

Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions

Get ready to program in a whole new way!

Functional Programming in Java will help you quickly get on top of the new, essential Java 8 language features and the functional style that will change and improve your code. This short, targeted book will help you make the paradigm shift from the old imperative way to a less error-prone, more elegant, and concise coding style that’s also a breeze to parallelize. You’ll explore the syntax and semantics of lambda expressions, method and constructor references, and functional interfaces. You’ll design and write applications better using the new standards in Java 8 and the JDK.

Get it Now!  

Leave a Reply


+ six = 10



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.

Sign up for our Newsletter

20,709 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books