Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Guide Rewrite #34

Merged
merged 9 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 1 addition & 15 deletions .github/workflows/continuous-integration-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,6 @@ jobs:
with:
java-version: '17'
distribution: 'temurin'
cache: maven

- name: Build Initial with Maven
working-directory: ./initial
run: ./mvnw --batch-mode clean package

- name: Build Initial with Gradle
working-directory: ./initial
run: ./gradlew build

- name: Build Complete with Maven
working-directory: ./complete
run: ./mvnw --batch-mode clean package

- name: Build Complete with Gradle
working-directory: ./complete
- name: Build with Gradle
run: ./gradlew build
18 changes: 18 additions & 0 deletions .github/workflows/reduce-asciidoc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Reduce README
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Install Asciidoctor Reducer
run: sudo gem install asciidoctor-reducer
- name: Reduce README
# to preserve preprocessor conditionals, add the --preserve-conditionals option
run: asciidoctor-reducer --preserve-conditionals -o README-reduced.adoc README.adoc
- name: Commit and Push README
uses: EndBug/add-and-commit@v9
with:
add: README-reduced.adoc
184 changes: 93 additions & 91 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,164 +3,166 @@
:icons: font
:source-highlighter: prettify
:project_id: gs-scheduling-tasks
:build_name: gs-scheduling-tasks
:build_version: 0.0.1-SNAPSHOT
:build_system: gradle
:java_version: 17
:spring_academy_available: n
:spring_academy_url:

This guide walks you through the steps for scheduling tasks with Spring.

== What You Will Build

You will build an application that prints out the current time every five seconds by using
Spring's `@Scheduled` annotation.
Spring Framework's https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html[`@Scheduled`^] annotation.

== What You Need
// rendered if building for Spring Academy
ifdef::env-build-for-spring-academy[]
// required: {project_id}
include::spring_academy_intro.adoc[]
endif::[]

:java_version: 1.8
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/main/prereq_editor_jdk_buildtools.adoc[]
// rendered if building for spring.io
ifndef::env-build-for-spring-academy[]

include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/main/how_to_complete_this_guide.adoc[]
//rendered if also available on Spring Academy
ifeval::["{spring_academy_available}" == "y"]
// required: {spring_academy_url}
include::spring_academy_see_also.adoc[]
endif::[]

// required: {java_version}, {project_id}
include::guide_intro.adoc[]

[[scratch]]
== Starting with Spring Initializr

You can use this https://start.spring.io/#!type=maven-project&language=java&packaging=jar&jvmVersion=11&groupId=com.example&artifactId=scheduling-tasks&name=scheduling-tasks&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.scheduling-tasks[pre-initialized project] and click Generate to download a ZIP file. This project is configured to fit the examples in this tutorial.
You can use this https://start.spring.io/#!type=gradle-project&language=java&packaging=jar&groupId=com.example&artifactId=scheduling-tasks&name=scheduling-tasks&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.scheduling-tasks[pre-initialized project^] and click Generate to download a ZIP file. This project is configured to fit the examples in this guide.

To manually initialize the project:

. Navigate to https://start.spring.io.
. Navigate to https://start.spring.io[https://start.spring.io^].
This service pulls in all the dependencies you need for an application and does most of the setup for you.
. Choose either Gradle or Maven and the language you want to use. This guide assumes that you chose Java.
. Choose either Gradle or Maven and the language you want to use. This guide assumes that you chose Java and Gradle.
. Click *Generate*.
. Download the resulting ZIP file, which is an archive of a web application that is configured with your choices.
. Download the resulting ZIP file, which is an archive of an application that is configured with your choices.

NOTE: If your IDE has the Spring Initializr integration, you can complete this process from your IDE.

NOTE: You can also fork the project from Github and open it in your IDE or other editor.

=== Adding the `awaitility` Dependency
endif::[]
// end render if building for spring.io

The tests in
`complete/src/test/java/com/example/schedulingtasks/ScheduledTasksTest.java`
require the `awaitility` library.

NOTE: Later versions of the `awaitility` library do not work for this test, so you have to
specify version 3.1.2.
== Enable Scheduling

To add the `awaitility` library to Maven, add the following dependency:
Although scheduled tasks can be embedded in web applications, the simpler approach (shown in this guide) creates a standalone application. To do so, package everything in a single, executable JAR file, driven by a Java main() method. The following snippet (from `src/main/java/com/example/schedulingtasks/SchedulingTasksApplication.java`) shows the application class:

====
[source,xml]
[source,java]
----
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>3.1.2</version>
<scope>test</scope>
</dependency>
@SpringBootApplication
@EnableScheduling
public class SchedulingTasksApplication {
----
====

The following listing shows the finished `pom.xml` file:
Spring Initializr adds the `@SpringBootApplication` annotation to our main class. `@SpringBootApplication` is a convenience annotation that adds all of the following:

====
[source,xml]
----
include::complete/pom.xml[]
----
====
- `@Configuration`: Tags the class as a source of bean definitions for the application
context.
- `@EnableAutoConfiguration`: Spring Boot attempts to automatically configure your Spring application based on the dependencies that you have added.
- `@ComponentScan`: Tells Spring to look for other components, configurations, and
services. If specific packages are not defined, recursive scanning begins with the package of the class that declares the annotation.

To add the `awaitility` library to Gradle, add the following dependency:
Additionally, add the `@EnableScheduling` annotation. This annotation enables Spring's scheduled task execution capability.

====
[source,text]
----
testImplementation 'org.awaitility:awaitility:3.1.2'
----
====
== Create a Scheduled Task

The following listing shows the finished `build.gradle` file:
Create a new class `src/main/java/com/example/schedulingtasks/ScheduledTasks.java` called:

====
[source,xml]
----
include::complete/build.gradle[]
[source,java]
----
====
@Component
public class ScheduledTasks {

[[initial]]
== Create a Scheduled Task
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);

Now that you have set up your project, you can create a scheduled task. The following
listing (from `src/main/java/com/example/schedulingtasks/ScheduledTasks.java`) shows how
to do so:
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

====
[source,java]
----
include::complete/src/main/java/com/example/schedulingtasks/ScheduledTasks.java[]
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
log.info("The time is now {}", dateFormat.format(new Date()));
}
}
----
====

The `Scheduled` annotation defines when a particular method runs.
The https://docs.spring.io/spring-framework/reference/integration/scheduling.html#scheduling-annotation-support-scheduled[`Scheduled` annotation^] defines when a particular method runs.

NOTE: This example uses `fixedRate`, which specifies the interval between method
invocations, measured from the start time of each invocation. There are
https://docs.spring.io/spring/docs/{spring_version}/spring-framework-reference/html/scheduling.html#scheduling-annotation-support-scheduled[other options],
such as `fixedDelay`, which specifies the interval between invocations measured from the
completion of the task. You can also use https://docs.spring.io/spring/docs/{spring_version}/javadoc-api/org/springframework/scheduling/support/CronSequenceGenerator.html[`@Scheduled(cron=". . .")`]
expressions for more sophisticated task scheduling.
NOTE: This example uses https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html#fixedRate()[`fixedRate()`^], which specifies the interval between method
invocations, measured from the start time of each invocation. Other options are https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html#cron()[`cron()`^] and https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html#fixedDelay()[`fixedDelay()`^]. For periodic tasks, exactly one of these three options must be specified, and optionally, https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html#initialDelay()[`initialDelay()`^]. For a one-time task, it is sufficient to just specify an initialDelay()

== Enable Scheduling
== Running the Application

Although scheduled tasks can be embedded in web applications and WAR files, the simpler
approach (shown in the next listing) creates a standalone application. To do so,
package everything in a single, executable JAR file, driven by a good old Java `main()`
method. The following listing (from
`src/main/java/com/example/schedulingtasks/SchedulingTasksApplication.java`) shows the
application class:
You should now be able to run the application by executing the main method in `SchedulingTasksApplication`. You can run the program from your IDE, or by executing the following Gradle command in the project root directory:
robertmcnees marked this conversation as resolved.
Show resolved Hide resolved

====
[source,java]
----
include::complete/src/main/java/com/example/schedulingtasks/SchedulingTasksApplication.java[]
./gradlew bootRun
----
====

include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/main/spring-boot-application-new-path.adoc[]
Doing so starts the application, and the method annotated with @Scheduled runs. You should see log messages similar to:

The
https://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#scheduling-enable-annotation-support[`@EnableScheduling`]
annotation ensures that a background task executor is created. Without it, nothing gets
scheduled.
====
----
20yy-mm-ddT07:23:01.665-04:00 INFO 19633 --- [ scheduling-1] c.e.schedulingtasks.ScheduledTasks : The time is now 07:23:01
20yy-mm-ddT07:23:06.663-04:00 INFO 19633 --- [ scheduling-1] c.e.schedulingtasks.ScheduledTasks : The time is now 07:23:06
20yy-mm-ddT07:23:11.663-04:00 INFO 19633 --- [ scheduling-1] c.e.schedulingtasks.ScheduledTasks : The time is now 07:23:11
----
====

include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/main/build_an_executable_jar_subhead.adoc[]
NOTE: This example uses https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/Scheduled.html#fixedRate()[`fixedRate()`^] scheduling, so the application runs indefinitely until you interrupt it manually.

include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/main/build_an_executable_jar_with_both.adoc[]
== Testing with the awaitility Dependency

Logging output is displayed, and you can see from the logs that it is on a background
thread. You should see your scheduled task fire every five seconds. The following listing
shows typical output:
To properly test your application, you can use the https://github.com/awaitility/awaitility[`awaitility` library^]. Since Spring Boot 3.2, this is a dependency that Boot manages. You can create a new test or view the existing test at `src/test/java/com/example/schedulingtasks/ScheduledTasksTest.java`:

====
[source,text]
[source,java]
----
...
2019-10-02 12:07:35.659 INFO 28617 --- [ scheduling-1] c.e.schedulingtasks.ScheduledTasks : The time is now 12:07:35
2019-10-02 12:07:40.659 INFO 28617 --- [ scheduling-1] c.e.schedulingtasks.ScheduledTasks : The time is now 12:07:40
2019-10-02 12:07:45.659 INFO 28617 --- [ scheduling-1] c.e.schedulingtasks.ScheduledTasks : The time is now 12:07:45
2019-10-02 12:07:50.657 INFO 28617 --- [ scheduling-1] c.e.schedulingtasks.ScheduledTasks : The time is now 12:07:50
...
@SpringBootTest
public class ScheduledTasksTest {

@SpyBean
ScheduledTasks tasks;

@Test
public void reportCurrentTime() {
await().atMost(Durations.TEN_SECONDS).untilAsserted(() -> {
verify(tasks, atLeast(2)).reportCurrentTime();
});
}
}
----
====

This test automatically runs when you run the `./gradlew clean build` task.

// required: {build_system} maven|gradle, {build_name}, {build_version}
// optional: {network_container}, {custom_hint_include_file}
include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/main/build_and_execute_guide.adoc[]

== Summary

Congratulations! You created an application with a scheduled task. Also, this technique
works in any type of application.
Congratulations! You created an application with a scheduled task.

== See Also

The following guides may also be helpful:

* https://spring.io/guides/gs/spring-boot/[Building an Application with Spring Boot]
* https://spring.io/guides/gs/batch-processing/[Creating a Batch Service]
* https://spring.io/guides/gs/spring-boot/[Building an Application with Spring Boot^]
* https://spring.io/guides/gs/batch-processing/[Creating a Batch Service^]

include::https://raw.githubusercontent.com/spring-guides/getting-started-macros/main/footer.adoc[]
14 changes: 9 additions & 5 deletions initial/build.gradle → build.gradle
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
plugins {
id 'org.springframework.boot' version '3.2.0'
id 'io.spring.dependency-management' version '1.1.4'
id 'java'
id 'org.springframework.boot' version '3.2.5'
id 'io.spring.dependency-management' version '1.1.4'
// id 'org.graalvm.buildtools.native' version '0.9.28'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

java {
sourceCompatibility = '17'
}

repositories {
mavenCentral()
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation('org.springframework.boot:spring-boot-starter-test')
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
tasks.named('test') {
useJUnitPlatform()
}
Binary file removed complete/.mvn/wrapper/maven-wrapper.jar
Binary file not shown.
2 changes: 0 additions & 2 deletions complete/.mvn/wrapper/maven-wrapper.properties

This file was deleted.

23 changes: 0 additions & 23 deletions complete/build.gradle

This file was deleted.

Binary file removed complete/gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
Loading
Loading