Déclaration par annotations

L’utilisation des méthodes de fabrique avec l’annotation @Bean permet d’ajouter des objets dans un contexte d’application sans être intrusif dans le code des classes concernées. Cela est surtout utile si nous voulons intégrer dans notre contexte d’application des beans à partir de classes dont nous ne pouvons pas ou ne voulons pas modifier le code source (par exemple des classes fournies par une bibliothèque tierce).

Pour les classes que nous développons spécifiquement pour notre application, il est plus simple d’utiliser des annotations supplémentaires. Nous allons voir un premier jeu d’annotations :

L’utilisation de ces annotations est plus intrusive puisqu’il faut les ajouter dans le code source des classes de nos beans. Heureusement, le Spring Framework autorise à mêler cette approche avec l’utilisation de méthodes de fabrique. Cela laisse donc une liberté complète aux développeurs pour définir leur contexte d’application.

L’annotation @Autowired

L’annotation @Autowired permet d’activer l’injection automatique de dépendance. Cette annotation peut être placée sur un constructeur, une méthode setter ou directement sur un attribut (même privé). Le Spring Framework va chercher le bean du contexte d’application dont le type est applicable à chaque paramètre du constructeur, aux paramètres de la méthode ou à l’attribut.

Si nous reprenons, notre exemple du chapitre précédent, nous pouvons simplifier l’implémentation de la classe WriterService.

Déclaration d’un attribut @Autowired
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package dev.gayerie.appstring;

import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired;

public class WriterService implements Runnable {

  @Autowired
  private Supplier<String> supplier;

  @Override
  public void run() {
    System.out.println(supplier.get());
  }

}

Nous n’avons plus besoin du constructeur. Spring est capable d’injecter un bean de type Supplier<String> (à condition qu’il n’en existe qu’un seul) dans l’attribut privé supplier. La dépendance n’est marquée que par la présence de cet attribut.

Notre programme principal devient maintenant :

Le programme principal
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package dev.gayerie.appstring;

import java.util.function.Supplier;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

public class TaskApplication {

  @Bean
  public Supplier<String> dataSupplier() {
    return new HardcodedSupplier();
  }

  @Bean
  public Runnable task() {
    return new WriterService();
  }

  public static void main(String[] args) throws InterruptedException {
    try (AnnotationConfigApplicationContext appCtx =
                  new AnnotationConfigApplicationContext(TaskApplication.class)) {
      appCtx.getBean(Runnable.class).run();
    }
  }
}

Note

Il peut arriver que la dépendance soit optionnelle, c’est-à-dire qu’un bean puisse ne pas être disponible dans le contexte d’application. Dans ce cas, nous pouvons positionner l’attribut required de @Autowired à false. La valeur ne sera pas positionnée mais la création du contexte d’application n’échouera pas. Il faut bien sûr adapter le code pour que le programme puisse s’exécuter malgré l’absence de cette dépendance

package dev.gayerie.appstring;

import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired;

public class WriterService implements Runnable {

  @Autowired(required = false)
  private Supplier<String> supplier;

  @Override
  public void run() {
    if (supplier == null) {
      System.out.println("Aucun fournisseur de données disponible.");
    } else {
      System.out.println(supplier.get());
    }
  }

}

Désambiguïsation avec @Primary et @Qualifier

Pour gérer automatiquement l’injection de dépendance, le Spring Framework se base sur le type du bean. Dans l’exemple ci-dessous :

package dev.gayerie.apptask;

import org.springframework.beans.factory.annotation.Autowired;

public class TaskManager() {

  @Autowired
  private Runnable tache;

  public void start() {
    // ...
  }

}

Spring va chercher un bean de type Runnable, c’est-à-dire un bean qui implémente l’interface Runnable dans le contexte d’application. Il est possible qu’il y ait plusieurs beans satisfaisant à ce critère. Dans ce cas, Spring va sélectionner le bean qui porte le même nom que l’attribut : tache dans notre exemple. Pour une méthode de fabrique, le nom du bean correspond au nom de la méthode ou à une des valeurs précisées dans l’attribut name de l’annotation @Bean :

Nom du bean équivalent au nom de la méthode
@Bean
public Runnable tache() {
  // ...
}
Nom du bean donné par l’annotation @Bean
@Bean(name = {"tache", "task"})
public Runnable getTask() {
  // ...
}

Note

Notez que le type de retour de la méthode de fabrique n’a pas vraiment d’importance pour Spring. Ce qui compte, c’est le type réel de l’objet qui est retourné.

S’il existe plusieurs beans compatibles avec le type attendu et qu’aucun d’eux ne porte le bon nom, alors la création du contexte d’application échoue avec l’exception UnsatisfiedDependencyException.

Dans une application, ces situations restent exceptionnelles. Cependant, lorsqu’elles surviennent, nous pouvons aider Spring à lever l’ambiguïté dans le choix du bean à injecter. Pour cela, nous disposons des annotations @Primary et @Qualifier.

L’annotation @Primary

L’annotation @Primary permet d’indiquer le bean qui devra être sélectionné en priorité en cas d’ambiguïté.

Utilisation de @Primary sur une méthode de fabrique
@Bean
@Primary
public Runnable oneTask() {
  // ...
}

@Bean
public Runnable anotherTash() {
  // ...
}

L’annotation @Qualifier

L’annotation @Qualifier permet de qualifier, c’est-à-dire de préciser le nom du bean à injecter. Dans la classe Java, on ajoute l’annotation sur un attribut ou sur un paramètre d’une méthode ou d’un constructeur.

Utilisation de @Qualifier sur un attribut
package dev.gayerie.apptask;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

public class TaskManager {

  @Autowired
  @Qualifier("tache")
  private Runnable runnable;

  public void executer() {
    runnable.run();
  }

}

Dans l’exemple ci-dessus, si plusieurs beans compatibles avec l’interface Runnable sont trouvés dans le contexte d’application, alors c’est celui portant le nom de tache qui sera choisi.

L’annotation @Value

L’annotation @Value est utilisable sur un attribut ou un paramètre pour un type primitif ou une chaîne de caractères. Elle donne la valeur par défaut à injecter.

Si nous reprenons notre exemple du chapitre précédent, nous pouvons créer un nouveau type de fournisseur de données :

Utilisation de @Value sur un attribut
package dev.gayerie.appstring;

import java.util.function.Supplier;

import org.springframework.beans.factory.annotation.Value;

public class ValueSupplier implements Supplier<String> {

  @Value("hello world")
  private String value;

  @Override
  public String get() {
    return value;
  }

}

Note

Le code précédent ne semble pas avoir beaucoup d’intérêt puisque nous aurions pu directement affecter la chaîne de caractères à l’attribut value. Nous verrons dans les chapitres Configuration d’une application et Le langage d’expression SpEL que cette annotation devient très utile pour charger des données depuis un fichier de configuration ou pour résoudre des dépendances de manière plus souple.

Les annotations JSR-250

Indépendamment du Spring Framework, la communauté Java a défini un ensemble d’annotations dans la spécification JSR-250. Certaines d’entre-elles sont reconnues par le Spring Framework :

@Resource

Cette Annotation peut se substituer à l’annotation @Autowired sur les attributs et les méthodes setter. Le Spring Framework réalise une injection de dépendance basée sur le type attendu. Si l’annotation spécifie un nom grâce à son attribut name alors l’injection de dépendance se fait en cherchant un bean du même nom.

Utilisation de @Resource sur un attribut
package dev.gayerie.apptask;

import javax.annotation.Resource;

public class TaskManager {

  @Resource(name = "tache")
  private Runnable runnable;

  public void executer() {
    runnable.run();
  }

}
@PostConstruct

Cette annotation s’utilise sur une méthode publique sans paramètre afin de signaler que cette méthode doit être appelée par le conteneur IoC après l’initialisation du bean. Il s’agit d’une alternative à la déclaration de la méthode d’initialisation.

@PreDestroy

Cette annotation s’utilise sur une méthode publique sans paramètre afin de signaler que cette méthode doit être appelée juste avant la fermeture du contexte d’application. Il s’agit d’une alternative à la déclaration de la méthode de destruction.

Note

Il est possible de déclarer plusieurs méthodes avec les annotations @PostConstruct ou @PreDestroy. Elles seront bien appelées par le framework. Par contre, s’il existe plusieurs méthodes d’initialisation (ou plusieurs méthodes de suppression), l’ordre de leur appel est indéterminé au moment de l’initialisation (ou de la destruction).

Nous pouvons facilement écrire une classe qui se comporte comme un fournisseur de connexion à une base de données en utilisant l’API JDBC. Le conteneur IoC sera responsable d’ouvrir la connexion et de fermer la connexion en appelant les méthodes d’initialisation et de destruction.

Implémentation simple d’un fournisseur de connexion à une base de données
package dev.gayerie.db;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.function.Supplier;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

public class SimpleConnectionProvider implements Supplier<Connection> {

  private String databaseUri;
  private String login;
  private String password;
  private Connection connection;

  public SimpleConnectionProvider(String databaseUri, String login, String password) {
    this.databaseUri = databaseUri;
    this.login = login;
    this.password = password;
  }

  @PostConstruct
  public void openConnection() throws SQLException {
    connection = DriverManager.getConnection(databaseUri, login, password);
  }

  @PreDestroy
  public void closeConnection() throws SQLException {
    if(connection != null) {
      connection.close();
    }
  }

  @Override
  public Connection get() {
    return connection;
  }
}

Détection automatique des composants (component scan)

Plutôt que de créer les beans avec des méthodes de fabrique, il est possible de demander au Spring Framework de rechercher dans des packages les classes qui doivent être instanciées pour ajouter des beans dans le contexte d’application. On appelle cette opération le component scan.

Pour activer cette fonctionnalité, il suffit de rajouter l’annotation @ComponentScan sur la classe qui est passée en paramètre de création du contexte d’application. Par défaut, le Spring Framework va chercher les classes dans le package de cette classe et tous les sous packages. Vous pouvez modifier ce comportement à l’aide de l’attribut basePackages qui vous permet de donner la liste des packages (incluant automatiquement leurs sous-packages) à scruter. Vous pouvez également utiliser l’attribut basePackageClasses pour fournir une liste de classes. Dans ce cas, ce sont les packages auxquelles appartiennent ces classes (en incluant automatiquement les sous-packages) qui seront scrutés.

Le framework va scruter toutes les classes et créer un bean dans le conteneur IoC pour celles qui sont identifiées comme des composants. Une classe désigne un composant si elle possède l’annotation @Component ou une annotation de stéréotype de composant (Cf. ci-dessous).

Nous pouvons reprendre notre exemple du chapitre précédent en réduisant considérablement le code en remplaçant les méthodes de fabrique par une détection automatique des composants :

Déclaration du fournisseur de données comme un composant
package dev.gayerie.appstring;

import java.util.function.Supplier;
import org.springframework.stereotype.Component;

@Component
public class HardcodedSupplier implements Supplier<String> {
  @Override
  public String get() {
    return "Hello world";
  }

}
Déclaration du service comme un composant
package dev.gayerie.appstring;

import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class WriterService implements Runnable {

  @Autowired
  private Supplier<String> supplier;

  @Override
  public void run() {
    System.out.println(supplier.get());
  }
}
Activation de la détection automatique de composants
package dev.gayerie.appstring;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

@ComponentScan
public class TaskApplication {
  public static void main(String[] args) throws InterruptedException {
    try (AnnotationConfigApplicationContext appCtx =
                  new AnnotationConfigApplicationContext(TaskApplication.class)) {
      appCtx.getBean(Runnable.class).run();
    }
  }
}

Notion de composant

Nous l’avons vu à la section précédente, une classe de composant est une classe annotée avec @Component. Si nécessaire, on peut spécifier le nom du bean comme valeur de l’annotation :

@Component("servicePrincipal")
public class WriterService implements Runnable {
  // ...
}

Si on ne fournit pas de nom pour le bean, le Spring Framework le déduit du nom de la classe en mettant la première lettre en minuscule. Par exemple le composant de la classe WriterService s’appellera par défaut writerService.

Comme les méthodes de fabrique, une classe de composant peut également être annotée avec @Primary. De plus, elle peut elle-même déclarer des méthodes de fabrique, c’est-à-dire des méthodes portant l’annotation @Bean. Dans ce cas, ces méthodes de fabrique seront appelées après la création du bean.

Si la classe du composant déclare un constructeur avec des paramètres, alors Spring essaiera de résoudre les dépendances pour chaque paramètre. Les paramètres du constructeur peuvent recevoir les annotations @Value ou @Qualifier pour guider le framework dans le processus d’injection. Si la classe du composant déclare plusieurs constructeurs alors vous devez ajouter l’annotation @Autowired sur le constructeur qui doit être utilisé par le Spring Framework.

Stéréotype de composant

Il est possible de donner un stéréotype au composant, c’est-à-dire de placer une annotation qui permet de comprendre plus précisément le rôle du composant dans notre application. Certains stéréotypes sont purement descriptifs et ne servent qu’à fournir une information aux développeurs. D’autres bénéficient d’un traitement particulier dans certains contextes d’exécution.

@Component est un stéréotype générique puisqu’il indique simplement que cette classe doit être utilisée pour instancier un bean. Les autres stéréotypes fournis par le Spring Framework sont :

@Service

Un service est un composant qui remplit une fonctionnalité centrale dans l’architecture d’une application. Il renvoie aux classes qui ont la charge de réaliser les fonctionnalités principales. Il s’agit normalement d’une classe qui ne maintient pas d’état conversationnel.

@Repository

Un repository (référentiel) désigne un composant qui représente un mécanisme permettant de stocker et de rechercher une collection d’objets. Par exemple, un DAO (Data Access Object) est une forme de repository qui réalise, la plupart du temps, une interface avec un système de gestion de base de données.

@Configuration

Un composant de configuration sert à configurer le contexte d’application. Généralement, il déclare des méthodes de fabrique annotées avec @Bean (Cf. ci-dessous).

@Controller et @RestController

Un composant qui joue le rôle d’un contrôleur dans une architecture MVC pour une application Web ou une API Web. Ces stéréotypes seront détaillés dans les chapitres Les applications Web avec Spring Web MVC et Les API Web avec Spring Web MVC.

Note

Les stéréotypes @Service et @Repository sont purement descriptifs. Ils permettent d’offrir une information supplémentaire aux développeurs. Ils renvoient directement à des notions décrites dans l’ouvrage de Eric Evans: Domain-Driven Design (Addison-Wesley 2003).

Si nous poursuivons notre exemple du chapitre précédent, alors le fournisseur de données se comporte comme un repository et le service comme un service (évidemment). Nous pouvons ainsi préciser les stéréotypes de chacun.

Déclaration du fournisseur comme repository
package dev.gayerie.appstring;

import java.util.function.Supplier;
import org.springframework.stereotype.Repository;

@Repository
public class HardcodedSupplier implements Supplier<String> {
  @Override
  public String get() {
    return "Hello world";
  }

}
Déclaration du service
package dev.gayerie.appstring4;

import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class WriterService implements Runnable {

  @Autowired
  private Supplier<String> supplier;

  @Override
  public void run() {
    System.out.println(supplier.get());
  }
}

Le stéréotype @Configuration

L’annotation @Configuration permet de déclarer pour Spring un composant qui ne sert qu’à configurer le contexte de l’application. Normalement ce composant n’est pas destiné à être injecté comme dépendance mais à déclarer des méthodes de fabrique annotées avec @Bean.

Exemple d’un bean de configuration
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package dev.gayerie.configuration;

import java.time.LocalTime;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TimeConfiguration {

  @Bean
  public LocalTime startTime() {
    return LocalTime.now();
  }

  @Bean
  public LocalTime endTime() {
     return startTime().plusMinutes(1);
  }
}

Dans l’exemple ci-dessus, le bean de configuration ajoute deux beans dans le contexte d’application, tous deux de type LocalTime : startTime et endTime qui pourront être injectés dans les composants où ces valeurs sont nécessaires.

Important

Dans notre exemple précédent, notez bien la ligne 17. La méthode endTime appelle la méthode startTime pour connaître l’heure de début et ainsi, calculer l’heure de fin. Cela peut sembler trivial et pourtant… startTime désigne un bean, donc la méthode startTime sera appelée une fois par Spring pour calculer l’heure de début à l’initialisation du contexte. Si la méthode endTime appelle également startTime, cela peut conduire à une date de début légèrement différente. Dit autrement, le bean startTime semble être créé deux fois : une fois par Spring et une fois par l’appel dans la méthode endTime. En fait l’annotation @Configuration nous prémunit de ce problème. Une classe annotée avec @Configuration est instrumentée par le Spring Framework de manière à se comporter comme attendu. Donc Spring nous garantit que le bean startTime utilisé par la méthode endTime est bien le même que celui ajouté dans le contexte de l’application.

Dans la documentation officielle de Spring, une différence est faite entre le lite @Bean mode et le full @Configuration mode. Si nous n’utilisons pas l’annotation @Configuration alors nous devons faire très attention à n’obtenir un bean de contexte d’application que par injection. Pour notre exemple précédent, nous serions obligé d’écrire :

Exemple d’un bean de configuration
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
package dev.gayerie.configuration;

import java.time.LocalTime;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class TimeConfiguration {

  @Bean
  public LocalTime startTime() {
    return LocalTime.now();
  }

  @Bean
  public LocalTime endTime(LocalTime startTime) {
     return startTime.plusMinutes(1);
  }
}

Cela peut sembler plus maladroit mais il faut se rappeler que lorsqu’on utilise un conteneur IoC, c’est lui qui a la responsabilité de créer et d’injecter les beans. On voit que l’annotation @Configuration n’est pas qu’un simple stéréotype et qu’elle introduit une facilité de programmation en nous masquant cette subtilité de comportement.

Astuce

En considérant la section précédente, il devient évident que la classe passée en paramètre de la création d’un contexte d’application devrait porter l’annotation @Configuration car elle agit comme le premier bean de configuration.

package dev.gayerie.demo;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyApplication {

  public static void main(String[] args) throws InterruptedException {
    try (AnnotationConfigApplicationContext appCtx =
                  new AnnotationConfigApplicationContext(MyApplication.class)) {
      // ...
    }
  }

  // ...

}

Un bean de configuration doit être scanné par le Spring Framework pour pouvoir être instancié et participer à la création du contexte d’application. Si vous développez des bibliothèques destinées à être réutilisées dans plusieurs applications ou si vous voulez introduire de la modularité dans l’architecture de votre application, vous pouvez fournir des beans de configuration pour chaque module et importer ces beans grâce à l’annotation @Import.

Imaginons que nous ayons isolé dans notre code un package et tous ses sous packages pour les classes gérant la sécurité dans notre application et un autre package et tous ses sous packages pour les classes gérant l’accès aux services nécessaires à notre application.

Dans le premier package, nous pouvons ajouter la classe SecurityConfiguration qui peut contenir des méthodes de fabrique et indiquer à Spring qu’il doit réaliser une détection automatique à partir de ce package :

package dev.gayerie.module.security;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class SecurityConfiguration {

  //..

}

Dans le second package, nous pouvons ajouter la classe ServiceAccessConfiguration qui peut contenir des méthodes de fabrique et indiquer à Spring qu’il doit réaliser une détection automatique à partir de ce package :

package dev.gayerie.module.service;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan
public class ServiceAccessConfiguration {

  //..

}

La classe représentant l’application principale avec la méthode main peut importer ces deux classes de configuration avec l’annotation @Import :

package dev.gayerie.module.app;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Import;
import dev.gayerie.module.security.SecurityConfiguration;
import dev.gayerie.module.service.ServiceAccessConfiguration;

@Import({SecurityConfiguration.class, ServiceAccessConfiguration.class})
public class MyApplication {

  public static void main(String[] args) throws InterruptedException {
    try (AnnotationConfigApplicationContext appCtx =
                  new AnnotationConfigApplicationContext(MyApplication.class)) {
      // ...
    }
  }

}

Création de stéréotype de composant

Il est extrêmement simple de créer ses propres stéréotypes avec le Spring Framework. Il suffit de créer sa propre annotation et de lui ajouter elle-même l’annotation @Component. Il ne faut pas oublier de préciser que notre annotation doit avoir une rétention de type RUNTIME pour être visible par le Spring Framework au lancement de l’application. Il est même possible de réaliser assez facilement des traitements spécifiques sur nos propres stéréotypes.

Imaginons que nous voulions créer un stéréotype @ConcurrentTask pour identifier des classes implémentant l’interface Runnable. Le conteneur IoC doit créer un bean pour chacune de ces classes et l’exécuter automatiquement au lancement de l’application en appelant sa méthode run. Nous commençons par créer l’annotation @ConcurrentTaks :

L’annotation @ConcurrentTask
package dev.gayerie.concurrent;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;

@Target(TYPE)
@Retention(RUNTIME)
@Documented
@Component
public @interface ConcurrentTask {

  @AliasFor(annotation = Component.class)
  String value() default "";

}

Note

Cette annotation est elle-même annotée avec @Component. Cela suffit au Spring Framework pour traiter toutes les classes portant cette annotation comme un composant.

Nous pouvons créer notre composant de configuration :

Le composant de configuration
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package dev.gayerie.concurrent;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConcurrentTaskConfiguration {

  @Bean(destroyMethod = "shutdown")
  public Executor executor(ApplicationContext appCtx) {
    Executor executor = Executors.newFixedThreadPool(4);
    for (String name : appCtx.getBeanNamesForAnnotation(ConcurrentTask.class)) {
      executor.execute(appCtx.getBean(name, Runnable.class));
    }
    return executor;
  }

}

Ce composant de configuration déclare une méthode de fabrique pour un exécuteur. Cette méthode crée un fixed thread pool à la ligne 15. Puis elle récupère et parcourt le nom de tous les beans du contexte d’application qui possèdent l’annotation @ConcurrentTask que nous avons créée. Ce traitement est rendu simple grâce à la méthode getBeanNamesForAnnotation qui est fournie par le contexte d’application. Il ne reste plus qu’à récupérer les beans par leur nom et à les ajouter à l’exécuteur. Notez que la méthode de fabrique attend directement une instance de ApplicationContext en paramètre. Cela permet d’avoir accès aux méthodes du contexte d’application.

En ajoutant ce composant de configuration dans notre application Spring finale, nous pouvons maintenant déclarer des classes implémentant l’interface Runnable et stéréotypées comme @ConcurrentTask. Elles seront automatiquement exécutées par notre application.

Un exemple de tâche concurrente
package dev.gayerie.concurrent;

@ConcurrentTask
public class HelloWorldTask implements Runnable {

  @Override
  public void run() {
    System.out.println("Hello world");
  }

}

Notions avancées pour la gestion des dépendances

Dans cette section, nous allons voir deux annotations permettant de résoudre des cas particuliers lors de l’initialisation d’un contexte d’application : @Order et @DependsOn.

L’annotation @Order

Parfois, nous désirons déclarer plusieurs beans qui héritent de la même classe ou qui implémentent la même interface. Mais l’ordre d’instanciation de ces beans a une importance. Pour reprendre l’exemple d’un gestionnaire de tâches, nous pouvons vouloir déclarer plusieurs beans représentant une tâche et dont les classes implémentent l’interface Runnable. Nous pourrions vouloir introduire un ordre dans ces tâches. Pour cela, nous allons utiliser l’annotation @Order :

La première tâche
package dev.gayerie.ordered_tasks;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(1)
public class TacheDebut implements Runnable {

  @Override
  public void run() {
    System.out.println("je suis la tâche de début");
  }
}
La seconde tâche
package dev.gayerie.ordered_tasks;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(2)
public class TacheIntermediaire implements Runnable {

  @Override
  public void run() {
    System.out.println("je suis la tâche intermédiaire");
  }
}
La dernière tâche
package dev.gayerie.ordered_tasks;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(3)
public class TacheFin implements Runnable {

  @Override
  public void run() {
    System.out.println("je suis la tâche de fin");
  }
}

Le programme principal se présente sous la forme d’un composant dans lequel on injecte la liste des instances de Runnable disponibles dans le contexte d’application :

L’application
package dev.gayerie.ordered_tasks;

import java.util.List;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;

@ComponentScan
@Component
public class MyApplication {

  @Autowired
  private List<Runnable> taches;

  @PostConstruct
  public void executer() {
    for (Runnable runnable : taches) {
      runnable.run();
    }
  }

  public static void main(String[] args) throws InterruptedException {
    try (AnnotationConfigApplicationContext appCtx =
                  new AnnotationConfigApplicationContext(MyApplication.class)) {

    }
  }

}

L’utilisation de l’annotation @Order nous garantit l’ordre des beans dans la liste injectée et la sortie du programme sera donc :

je suis la tâche de début
je suis la tâche intermédiaire
je suis la tâche de fin

Note

Il est rare que nous ayons besoin de représenter un ordre dans un ensemble de beans. Mais cela peut arriver lorsque nous voulons déclarer des filtres de traitement pour une requête HTTP. Spring MVC accepte des composants implémentant l’interface HandlerInterceptor et il utilise automatiquement ces beans pour réaliser des traitements avant et après la prise en charge de la requête HTTP par un contrôleur. Selon le type de traitements que nous voulons mettre en place, l’ordre peut être déterminant et il est alors pratique de pouvoir ajouter une annotation @Order sur chaque classe implémentant HandlerInterceptor.

L’annotation @DependsOn

Une dépendance est marquée par la présence d’un attribut dans une classe dont la valeur doit être injectée. Cela permet à Spring de déterminer l’ordre dans lequel les beans doivent être créés. Mais il arrive parfois que la dépendance ne soit pas toujours aussi évidente. C’est notamment le cas quand un bean dépend d’un effet de bord créé par un autre bean.

Support des annotations standard JSR-330

La communauté Java a proposé des annotations pour déclarer des injections de dépendance. Elles sont décrites le standard JSR-330. Le Spring Framework supporte également ces annotations.

Note

La JSR-330 ne fait pas partie de l’API du langage Java, pour pouvoir l’utiliser vous devez ajouter une dépendance. Pour un projet Maven :

<dependency>
  <groupId>javax.inject</groupId>
  <artifactId>javax.inject</artifactId>
  <version>1</version>
</dependency>

Les annotations fournies par cette dépendance qui peuvent être utilisées dans une application Spring :

@Inject

Vous pouvez utiliser l’annotation @Inject à la place de @Autowired.

@Named

Vous pouvez utiliser l’annotation @Named à la place de @Component. L’annotation @Named peut également être utilisée conjointement avec @Inject pour préciser le nom du bean à injecter.

Note

Pour une comparaison plus poussée entre JSR-330 et le Spring Framework, reportez-vous à la documentation de ce dernier.