I. Projet Spring Batch▲
I-A. Projet Maven▲
Dans cette partie, nous allons créer un projet batch à l'aide de Spring et de Maven. Pour cela, créez un projet Maven vierge dans votre IDE préféré, pour exemple, mon pom.xml :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
<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.soat.batch</groupId>
<artifactId>
scheduler</artifactId>
<version>
0.0.1-SNAPSHOT</version>
<packaging>
jar</packaging>
<name>
batch-scheduler</name>
<properties>
<project.build.sourceEncoding>
UTF-8</project.build.sourceEncoding>
<!-- propriétés utilisées dans la dernière partie de cet article -->
<java.version>
1.7</java.version>
<package.directory>
${project.build.directory}/${name}</package.directory>
<main.class>
com.soat.batch.App</main.class>
<!-- version de Spring batch -->
<spring-batch.version>
3.0.0.RELEASE</spring-batch.version>
</properties>
<dependencies>
<dependency>
<groupId>
org.springframework.batch</groupId>
<artifactId>
spring-batch-core</artifactId>
<version>
${spring-batch.version}</version>
</dependency>
</dependencies>
</project>
La dépendance spring-batch-core est suffisante pour notre projet, puisqu'elle contient les bibliothèques du framework Spring.
I-B. Configurer Spring▲
Nous allons maintenant démarrer le développement de notre programme, en commençant par la configuration Spring :
2.
3.
4.
5.
6.
7.
8.
9.
@Configuration
@EnableBatchProcessing
@PropertySource
(
"classpath:batch.properties"
)
public
class
AppConfiguration {
@Bean
public
static
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer
(
) {
return
new
PropertySourcesPlaceholderConfigurer
(
);
}
}
Cette classe est très utile, car l'annotation @EnableBatchProcessing nous crée gentiment les beans nécessaires au fonctionnement d'un batch Spring :
- jobRepository ;
- transactionManager ;
- jobLauncher.
I-C. Configurer Spring Batch▲
Créons à présent les classes spécifiques à un batch Spring.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
@Component
public
class
BatchTasklet implements
Tasklet {
@Value
(
"${tasklet.message:@null}"
) private
String message;
@Override
public
RepeatStatus execute
(
StepContribution contribution, ChunkContext chunkContext) throws
Exception {
System.out.println
(
message);
return
RepeatStatus.FINISHED;
}
}
Pour des raisons évidentes de simplicité, je ne crée qu'une tâche (tasklet), qui affiche un message récupéré de mon fichier batch.properties ; j'assigne ensuite cette tâche à mon job configuré dans le fichier suivant.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
<?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
:
batch
=
"http://www.springframework.org/schema/batch"
xmlns
:
task
=
"http://www.springframework.org/schema/task"
xsi
:
schemaLocation
=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd"
>
<
context
:
component-scan
base-package
=
"com.soat.batch"
/>
<
batch
:
job
id
=
"scheduledJob"
>
<
batch
:
step
id
=
"jobStep"
>
<
batch
:
tasklet
ref
=
"batchTasklet"
/>
</
batch
:
step>
</
batch
:
job>
</beans>
Il n'y a rien de complexe dans ce fichier, c'est la configuration du job qui contient notre tâche. C'est ce job qui va être lancé par notre programme au travers de la classe suivante :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
@Component
public
class
BatchLauncher {
@Autowired
@Qualifier
(
"scheduledJob"
)
private
Job job;
@Autowired
private
JobLauncher jobLauncher;
public
void
run
(
) {
JobParameters parameters =
new
JobParametersBuilder
(
)
.addLong
(
"currentTime"
, new
Long
(
System.currentTimeMillis
(
)))
.toJobParameters
(
);
try
{
jobLauncher.run
(
job, parameters);
}
catch
(
JobExecutionAlreadyRunningException e) {
}
catch
(
JobRestartException e) {
}
catch
(
JobInstanceAlreadyCompleteException e) {
}
catch
(
JobParametersInvalidException e) {
}
}
}
On récupère le job configuré ainsi que le launcher de Spring Batch pour créer le point d'entrée de notre batch.
On ajoute au lancement du jobLauncher de Spring un timestamp qui va permettre la récurrence puisque les différentes instances d'un même job doivent avoir des paramètres différents dans Spring Batch.
I-D. Création de la classe principale▲
2.
3.
4.
5.
6.
7.
8.
9.
public
class
App {
public
static
void
main
(
String[] args) {
try
(
ClassPathXmlApplicationContext context =
new
ClassPathXmlApplicationContext
(
"job-config.xml"
)) {
BatchLauncher launcher =
(
BatchLauncher) context.getBean
(
BatchLauncher.class
);
launcher.run
(
);
}
}
}
Ceci est notre classe principale, c'est-à-dire le point d'entrée de notre batch. On remarque que c'est la même structure qu'une application console classique ; il suffit ensuite de déclarer le contexte Spring afin de pouvoir récupérer le bean BatchLauncher qui fait tourner notre programme.
Cet article n'a pas pour objectif de montrer les différentes façons de créer un batch avec Spring : c'est pourquoi notre exemple reste très simpliste.
II. Ajout du Scheduler à notre batch▲
Dans le fichier job-config.xml, configuré dans la section précédente, il suffit d'ajouter quelques lignes de code pour configurer le scheduler. Afin de lancer notre programme de façon récurrente, il faut passer, dans la configuration, la référence du bean qui sert de point d'entrée du batch : BatchLauncher (avec un b minuscule pour les conventions de nommage). Puis, il ne nous reste qu'à choisir le type de récurrence : fixed-delay, fixed-rate, cron.
2.
3.
<
task
:
scheduled-tasks>
<
task
:
scheduled
ref
=
"batchLauncher"
method
=
"run"
fixed-delay
=
"5000"
/>
</
task
:
scheduled-tasks>
Avec cette configuration, votre tâche va s'exécuter toutes les cinq secondes à partir de la fin de la tâche précédente.
2.
3.
<
task
:
scheduled-tasks>
<
task
:
scheduled
ref
=
"batchLauncher"
method
=
"run"
fixed-rate
=
"5000"
/>
</
task
:
scheduled-tasks>
Avec cette configuration, votre tâche va s'exécuter toutes les cinq secondes à partir du début de la tâche précédente.
2.
3.
<
task
:
scheduled-tasks>
<
task
:
scheduled
ref
=
"batchLauncher"
method
=
"run"
cron
=
"5 30 6 * * 1-5"
/>
</
task
:
scheduled-tasks>
Avec cette configuration, votre tâche va s'exécuter tous les jours ouvrés (lundi-vendredi) à 6 heures 30 minutes et 5 secondes du matin.
On remarque ici une extension du cron Unix avec l'ajout des secondes.
C'est donc Spring Batch qui s'occupe de lancer notre tâche au travers du BatchLauncher. Certaines lignes doivent être enlevées de App.java, qui ne nécessite plus l'appel explicite de la méthode run du bean BatchLauncher :
2.
3.
4.
5.
6.
public
class
App {
public
static
void
main
(
String[] args) {
new
ClassPathXmlApplicationContext
(
"job-config.xml"
);
}
}
Pour améliorer un peu ces configurations, je préfère mettre la valeur de la récurrence dans un fichier .properties qui me permettra de la modifier sans avoir besoin de recompiler le batch.
2.
batch.message=batch lancé !
batch.cron=5 30 6 * * 1-5
C'est notre fichier batch.properties configuré dans AppConfiguration.java.
III. Préparation du package▲
Afin de pouvoir livrer le batch, il faut préparer le package nécessaire. Pour cela, nous allons utiliser Maven et quelques-uns de ses plugins. Dans le fichier pom.xml ajoutons la section build.
2.
3.
4.
5.
6.
7.
8.
<dependencies>
...
</dependencies>
<build>
<plugins>
<!-- placer les plugins ici -->
</plugins>
</build>
Le premier plugin va nous permettre de réinitialiser le package, cela supprime la build précédente pour la remplacer ensuite par le nouveau package que les autres plugins vont créer.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-clean-plugin</artifactId>
<version>
2.6.1</version>
<executions>
<execution>
<id>
clean-package</id>
<phase>
prepare-package</phase>
<goals>
<goal>
clean</goal>
</goals>
<configuration>
<excludeDefaultDirectories>
true</excludeDefaultDirectories>
<filesets>
<fileset>
<directory>
${package.directory}</directory>
<includes>
<include>
*</include>
<include>
**</include>
</includes>
</fileset>
</filesets>
</configuration>
</execution>
</executions>
</plugin>
Le prochain plugin sert à la compilation du package pour la version de Java souhaitée.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-compiler-plugin</artifactId>
<version>
3.3</version>
<executions>
<execution>
<id>
compile-package</id>
<phase>
compile</phase>
<goals>
<goal>
compile</goal>
</goals>
<configuration>
<source>
${java.version}</source>
<target>
${java.version}</target>
</configuration>
</execution>
</executions>
</plugin>
On va maintenant packager le projet en .jar en excluant le fichier batch.properties qui pourra alors être modifié sans recompilation du batch. Il faudra néanmoins relancer le programme pour qu'il prenne en compte les modifications. La configuration du manifeste est très importante pour que votre application fonctionne, car c'est dans ce fichier que se trouve la déclaration de la classe principale ainsi que le classpath pour accéder aux dépendances.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-jar-plugin</artifactId>
<version>
2.6</version>
<executions>
<execution>
<id>
jar-package</id>
<phase>
package</phase>
<goals>
<goal>
jar</goal>
</goals>
<configuration>
<finalName>
${artifactId}-${version}</finalName>
<outputDirectory>
${package.directory}/</outputDirectory>
<excludes>
<!-- les fichiers .properties ne seront pas dans le jar -->
<exclude>
*.properties</exclude>
</excludes>
<archive>
<manifest>
<addClasspath>
true</addClasspath>
<classpathPrefix>
dependencies/</classpathPrefix>
<mainClass>
${main.class}</mainClass>
</manifest>
<manifestEntries>
<Class-Path>
.</Class-Path>
</manifestEntries>
</archive>
</configuration>
</execution>
</executions>
</plugin>
Je préfère sortir toutes les bibliothèques externes (dépendances Maven) en dehors du jar dans un dossier que vous pouvez appeler dependencies, ou bien libs, comme bon vous semble : cela permet de créer un jar qui ne contient que les classes de votre programme.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-dependency-plugin</artifactId>
<version>
2.10</version>
<executions>
<execution>
<id>
copy-dependencies</id>
<phase>
package</phase>
<goals>
<goal>
copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>
${package.directory}/dependencies/</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
Nous avons exclu les fichiers .properties (ressources) du jar, donc il nous faut les intégrer dans le package grâce à ce plugin. Nous voulons que les fichiers xml de configuration Spring restent dans le jar donc, à l'inverse de ce que nous avions fait pour le plugin jar, nous devons exclure les .xml des ressources à sortir.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
<plugin>
<groupId>
org.apache.maven.plugins</groupId>
<artifactId>
maven-resources-plugin</artifactId>
<version>
2.7</version>
<executions>
<execution>
<id>
copy-resources</id>
<phase>
package</phase>
<goals>
<goal>
copy-resources</goal>
</goals>
<configuration>
<outputDirectory>
${package.directory}</outputDirectory>
<resources>
<resource>
<directory>
src/main/resources/</directory>
<excludes>
<!-- le fichier job-config.xml reste dans le jar -->
<exclude>
*.xml</exclude>
</excludes>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
Il suffit de lancer un maven install pour packager le projet dans le dossier target/batch-scheduler.
Ensuite, dans votre invite de commandes préférée, jouer le batch d'un simple.
java -jar scheduler-0.0.1-SNAPSHOT.jar
IV. Conclusion et remerciement▲
Un batch récurrent est une fonctionnalité très utile dans un projet, mais sa création peut s'avérer assez complexe. Cet article expose une façon de faire particulièrement simple avec le framework Spring ; il ne tient qu'à vous maintenant d'y intégrer la logique propre à votre projet et je vous conseille de bien étudier le framework batch avant (si cela n'est pas déjà fait).
Si vous avez différents batchs à créer qui doivent travailler à des moments différents, il est possible de les séparer dans le même projet en ajoutant une ligne task :scheduled avec son propre timer réduisant ainsi le nombre de projets à maintenir.
La préparation du package de livraison est une manière que j'ai trouvée élégante pour procéder, mais cela peut paraître assez complexe à mettre en place en sachant qu'un EDI vous permet de tout compiler dans un jar : c'est un petit plus sur l'utilisation de Maven, mais pas une nécessité pour créer un batch.
Cet article a été publié avec l'aimable autorisation de SOAT, société d'expertise et de conseil en informatique.
Nous tenons à remercier ClaudeLeloup pour la relecture orthographique et Mickaël Baron pour la mise au gabarit.