6. Developing your first Spring Cloud Task application

A good place to start is with a simple "Hello World!" application so we’ll create the Spring Cloud Task equivalent to highlight the features of the framework. We’ll use Apache Maven as a build tool for this project since most IDEs have good support for it.

[Note]Note

The spring.io web site contains many “Getting Started” guides that use Spring Boot. If you’re looking to solve a specific problem; check there first. You can shortcut the steps below by going to start.spring.io and creating a new project. This will automatically generate a new project structure so that you can start coding right the way. Check the documentation for more details.

Before we begin, open a terminal to check that you have valid versions of Java and Maven installed.

$ java -version
java version "1.8.0_31"
Java(TM) SE Runtime Environment (build 1.8.0_31-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.31-b07, mixed mode)
$ mvn -v
Apache Maven 3.2.3 (33f8c3e1027c3ddde99d3cdebad2656a31e8fdf4; 2014-08-11T15:58:10-05:00)
Maven home: /usr/local/Cellar/maven/3.2.3/libexec
Java version: 1.8.0_31, vendor: Oracle Corporation
[Note]Note

This sample needs to be created in its own folder. Subsequent instructions assume you have created a suitable folder and that it is your "current directory".

6.1 Creating the POM

We need to start by creating a Maven pom.xml file. The pom.xml is the recipe that will be used to build your project. Open your favorite text editor and add the following:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

	<modelVersion>4.0.0</modelVersion>

	<groupId>com.example</groupId>
	<artifactId>myproject</artifactId>
	<packaging>jar</packaging>
	<version>0.0.1-SNAPSHOT</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.2.RELEASE</version>
	</parent>

	<properties>
		<start-class>com.example.SampleTask</start-class>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

This should give you a working build. You can test it out by running mvn package (you can ignore the "jar will be empty - no content was marked for inclusion!" warning for now).

[Note]Note

At this point you could import the project into an IDE (most modern Java IDE’s include built-in support for Maven). For simplicity we will continue to use a plain text editor for this example.

6.2 Adding classpath dependencies

A Spring Cloud Task is made up of a Spring Boot application that is expected to end. In our POM above, we created the shell of a Spring Boot application from a dependency perspective by setting our parent to use the spring-boot-starter-parent.

Spring Boot provides a number of additional "Starter POMs". Some of which are appropriate for use within tasks (spring-boot-starter-batch, spring-boot-starter-jdbc, etc) and some may not be ('spring-boot-starter-web` is probably not going to be used in a task). The indicator of if a starter makes sense or not comes down to if the resulting application will end (batch based applications typically end, the spring-boot-starter-web dependency bootstraps a servlet container which probably wont').

For this example, we’ll only need to add a single additional dependency, the one for Spring Cloud Task itself:

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-task-core</artifactId>
			<version>1.2.1.RELEASE</version>
		</dependency>

6.3 Writing the code

To finish our application, we need to create a single Java file. Maven will compile the sources from src/main/java by default so you need to create that folder structure. Then add a file named src/main/java/com/example/SampleTask.java:

package com.example;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.task.configuration.EnableTask;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
@EnableTask
public class SampleTask {

	@Bean
	public CommandLineRunner commandLineRunner() {
		return new HelloWorldCommandLineRunner();
	}

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

	public static class HelloWorldCommandLineRunner implements CommandLineRunner {

		@Override
		public void run(String... strings) throws Exception {
			System.out.println("Hello World!");
		}
	}
}

While it may not look like much, quite a bit is going on. To read more about the Spring Boot specifics, take a look at their reference documentation here: http://docs.spring.io/spring-boot/docs/current/reference/html/

We’ll also need to create an application.properties in src/main/resources. We’ll configure two properties in it: the application name (which is translated to the task name) and we’ll set the logging for spring cloud task to DEBUG so that we can see what’s going on:

logging.level.org.springframework.cloud.task=DEBUG
spring.application.name=helloWorld

6.3.1 The @EnableTask annotation

The first non boot annotation in our example is the @EnableTask annotation. This class level annotation tells Spring Cloud Task to bootstrap it’s functionality. This occurs by importing an additional configuration class, SimpleTaskConfiguration by default. This additional configuration registers the TaskRepository and the infrastructure for its use.

Out of the box, the TaskRepository will use an in memory Map to record the results of a task. Obviously this isn’t a practical solution for a production environment since the Map goes away once the task ends. However, for a quick getting started experience we use this as a default as well as echoing to the logs what is being updated in that repository. Later in this documentation we’ll cover how to customize the configuration of the pieces provided by Spring Cloud Task.

When our sample application is run, Spring Boot will launch our HelloWorldCommandLineRunner outputting our "Hello World!" message to standard out. The TaskLifecyceListener will record the start of the task and the end of the task in the repository.

6.3.2 The main method

The main method serves as the entry point to any java application. Our main method delegates to Spring Boot’s SpringApplication class. You can read more about it in the Spring Boot documentation.

6.3.3 The CommandLineRunner

In Spring, there are many ways to bootstrap an application’s logic. Spring Boot provides a convenient method of doing so in an organized manner via their *Runner interfaces (CommandLineRunner or ApplicationRunner). A well behaved task will bootstrap any logic via one of these two runners.

The lifecycle of a task is considered from before the *Runner#run methods are executed to once they are all complete. Spring Boot allows an application to use multiple *Runner implementation and Spring Cloud Task doesn’t attempt to impede on this convention.

[Note]Note

Any processing bootstrapped from mechanisms other than a CommandLineRunner or ApplicationRunner (using InitializingBean#afterPropertiesSet for example) will not be recorded by Spring Cloud Task.

6.4 Running the example

At this point, your application should work. Since this application is Spring Boot based, we can run it from the command line via the command $ mvn spring-boot:run from the root of our applicaiton:

$ mvn clean spring-boot:run
....... . . .
....... . . . (Maven log output here)
....... . . .


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.3.3.RELEASE)

2016-01-25 11:08:10.183  INFO 12943 --- [           main] com.example.SampleTask                   : Starting SampleTask on Michaels-MacBook-Pro-2.local with PID 12943 (/Users/mminella/Documents/IntelliJWorkspace/spring-cloud-task-example/target/classes started by mminella in /Users/mminella/Documents/IntelliJWorkspace/spring-cloud-task-example)
2016-01-25 11:08:10.185  INFO 12943 --- [           main] com.example.SampleTask                   : No active profile set, falling back to default profiles: default
2016-01-25 11:08:10.226  INFO 12943 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2a2c3676: startup date [Mon Jan 25 11:08:10 CST 2016]; root of context hierarchy
2016-01-25 11:08:11.051  INFO 12943 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2016-01-25 11:08:11.065  INFO 12943 --- [           main] o.s.c.t.r.support.SimpleTaskRepository   : Creating: TaskExecution{executionId=0, externalExecutionID='null', exitCode=0, taskName='application', startTime=Mon Jan 25 11:08:11 CST 2016, endTime=null, statusCode='null', exitMessage='null', arguments=[]}
Hello World!
2016-01-25 11:08:11.071  INFO 12943 --- [           main] com.example.SampleTask                   : Started SampleTask in 1.095 seconds (JVM running for 3.826)
2016-01-25 11:08:11.220  INFO 12943 --- [       Thread-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@2a2c3676: startup date [Mon Jan 25 11:08:10 CST 2016]; root of context hierarchy
2016-01-25 11:08:11.222  INFO 12943 --- [       Thread-1] o.s.c.t.r.support.SimpleTaskRepository   : Updating: TaskExecution{executionId=0, externalExecutionID='null', exitCode=0, taskName='application', startTime=Mon Jan 25 11:08:11 CST 2016, endTime=Mon Jan 25 11:08:11 CST 2016, statusCode='null', exitMessage='null', arguments=[]}
2016-01-25 11:08:11.222  INFO 12943 --- [       Thread-1] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

If you notice, there are three lines of interest in the above output:

  • SimpleTaskRepository logged out the creation of the entry in the TaskRepository.
  • The execution of our CommandLineRunner, demonstrated by the "Hello World!" output.
  • SimpleTaskRepository logging the completion of the task in the TaskRepository.
[Note]Note

A simple task application can be found in the samples module of the Spring Cloud Task Project here.

6.5 Writing your test

When writing your unit tests for a Spring Cloud Task application we have to keep in mind that Spring Cloud Task closes the context at the completion of the task as discussed here. If you are using Spring Framework’s testing functionality to manage the application context, you’ll want to turn off Spring Cloud Task’s auto-closing of the context. Add the following line: @TestPropertySource(properties = {"spring.cloud.task.closecontext_enable=false"}) to your tests will keep the context open. For example:

@RunWith(SpringRunner.class)
@SpringBootTest
@TestPropertySource(properties = {"spring.cloud.task.closecontext_enabled=false"})
public class DemoApplicationTests {

	@Test
	public void contextLoads() {
	//your test here
	}

}