Java : comment relancer une méthode plusieurs fois sans écrire du code ?

java8    image_retry

 

L'objet de ce billet est de répondre par la pratique à la question du titre. J'insiste sur un détail qui ne l'est pas: Sans écrire du code.

Plus précisément, je possède une classe java ayant une méthode qui se connecte à un support (réseau, périphérique,..) mais qui pourrait ne pas répondre au premier coup. Je souhaite alors que l'action de la méthode puisse être relancée (retry) un certain nombre de fois à intervalle régulier.

Notons que le besoin de relance de la méthode est transverse à tous les métiers, on voit que l'AOP peut apporter une solution.

Justement, c'est possible de le faire et surtout sans écrire du code de plomberie à l'aide de l'API JCABI et ses annotations (& l'AOP).

Comment faire ?

L'api JCABI, à l'instar de ce que fait le framework "Lombok", fournit quelques annotations utiles dans ce contexte.

Pour les pressés, il suffit d'annoter votre méthode avec @RetryOnFailure(attempts=4,delay=15,unit=TimeUnit.SECONDS) et le tour est joué. Cela signifie donc que la méthode va être relancée quatre fois avec une pause toutes les 15 secondes entre deux tentatives.

Et comme une démo pratique vaut mieux qu'un long discours, créons un projet maven & java 8 en suivant scrupuleusement les étapes suivantes:

Etape 1. Configurer le pom du projet maven

Dans cette étape deux actions sont à faire:

  • On ajoute la dépendance jcabi,
  • On configure aussi le plugin maven nommé jcabi-maven-plugin.
    Une attention particulière doit être observée pour la config du plugin maven.

NB. Le pom.xml en entier est fourni en annexe.

Etape 2. Ecrire une méthode puis l'annoter

Dans cette étape, nous allons créer notre méthode candidat et l'annoter avec @RetryOnFailure :

Pour cela créons une classe nommée ServiceHelper ayant cette méthode qui simule une connexion. Le code suivant permet de constater qu'elle est déjà annotée avec @RetryOnFailure.

Il n'y a rien à expliquer car les paramètres sont explicitement nommés.

Puisque l'implémentation de la méthode lève une exception, quatre relances du traitement vont être effectuées.

A noter que l'annotation @NetapsysLogger est une annotation spécifique qui ne concerne pas du tout le thème du billet. Mais elle peut être dé-commentée pour avoir de jolies traces de logs à condition de rajouter une annotation custom. J'y reviendrai une autre fois.

Revenons à l'essentiel de ce billet, le code de la classe ServiceHelper. Rien d'intéressant si ce n'est que la méthode lève une exception exprès.

 

package fr.abdou;

import java.net.UnknownHostException;
import java.util.concurrent.TimeUnit;
import com.jcabi.aspects.*;

public class ServiceHelper implements IServiceHelper {
 @RetryOnFailure(attempts=4,delay=2,unit=TimeUnit.SECONDS)
 //@NetapsysLogger
 public void simuleConnect(String msg) throws UnknownHostException {
	 System.out.println("method simuleConnect arg :  "+ msg);
	 throw new UnknownHostException();		
	}
}
ServiceHelper

Etape 3. Vérifier que le plugin Jcabi est bien configuré

J'ajoute cette étape car elle est absolument nécessaire.

Lancez la commande maven:

mvn jcabi:ajc

Cette commande va générer des  woven classes (voir weaver)  qui permettent de gérer, entre autres, l'annotation @RetyrOnFailure.

Etape 4. Tester avec une classe Main

La classe pour tester n'a aucun intérêt:

package fr.abdou;

public class MainJcabi {
	public static void main(String[] args) {
		IServiceHelper service = new ServiceHelper();
		try {
			service.simuleConnect("jcabi en action!");
		} catch (Exception e) {
			//
		}
	}
}

Etape 5. Résultats du test : Jcabi en action

L'exécution de la méthode main donne ceci (extrait des quatre essais):

.... [main] WARN  fr.abdou.ServiceHelper - #simuleConnect(): attempt #1 of 4 failed in 10ms  
        with java.net.UnknownHostException
	at fr.abdou.ServiceHelper.simuleConnect_aroundBody0(ServiceHelper.java:17) ....


.... [main] WARN  fr.abdou.ServiceHelper - #simuleConnect(): attempt #2 of 4 failed in 336µs (2s waiting already) 
      ......  
sorties main

 

RÉSUMÉ

Le traitement susceptible d'être relancé pour défaillance prévisible peut être annoté à l'aide de @RetyrOnFailure en fixant comme paramètres:

  • Un nombre raisonnable d'essais,
  • Une pause entre les deux essais.

Attention bien évidemment à l'abus des bonnes choses.

Signalons peut être que le plugin maven de jcabi est la pièce fragile si on l'utilise dans l'IDE.

ANNEXE1.

Le contenu en entier du pom du projet démo. Pensez à l'adapter.

<?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>fr.abdou</groupId>
	<artifactId>demoJCABIAndTests</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
	<name>demoJCABIAndtests</name>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>com.jcabi</groupId>
			<artifactId>jcabi-aspects</artifactId>
			<version>0.22.2</version>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>1.8.3</version>
			<scope>runtime</scope>
		</dependency>
	</dependencies>
	<build>
	 <plugins>
		<plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.3</version>
        <configuration>
			<source>${java.version}</source>
			<target>${java.version}</target>
		</configuration>
      </plugin>
	  <plugin>
				<groupId>com.jcabi</groupId>
				<artifactId>jcabi-maven-plugin</artifactId>
				<version>0.10</version>
				<configuration>
					<source>${java.version}</source>
					<target>${java.version}</target>
				</configuration>
				<executions>
					<execution>
					<id>ajc</id>
					<phase>compile</phase> 
					 <goals><goal>ajc</goal></goals>
					</execution>
				</executions>
				<dependencies>
					<dependency> 
						<groupId>org.aspectj</groupId> 
						<artifactId>aspectjtools</artifactId> 
						<version>1.8.3</version> 
					</dependency>
					<dependency>
						<groupId>org.hibernate</groupId>
						<artifactId>hibernate-validator</artifactId>
						<version>5.2.2.Final</version>
						<scope>runtime</scope>
					</dependency>
				</dependencies>
		</plugin>
	 </plugins>
	</build>
</project>

 

Update.15/02/16

A la fin de cet article, j'ai découvert une API spring-retry extraite de spring-batch qui permet de réaliser retry via retryTemplate (comme c'est le cas souvent en Spring). Je reviendrai ce sujet prochainement.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.