Les annotations de base du Framework Spring

Introduction

Spring Framework

Le framework Spring supporte un large ensemble d’annotations pour gérer différentes situations. Dans cet article, nous allons explorer les annotations dans le cadre de base de Spring.

Nous aborderons les annotations du noyau de Spring telles que @Autowire, @Qualifier, @Configuration, @Bean, @ComponentScan, @Lazy, et @Value.
Nous allons couvrir toutes ces annotations avec des exemples.

 


 

@Autowire

Depuis Spring 2.5, le framework a introduit l’injection de dépendances basée sur les annotations. L’annotation principale qui fait partie de cette fonctionnalité est @Autowired.
Elle permet à Spring de résoudre et d’injecter des “beans” collaboratifs dans notre bean.

L’annotation @Autowired est le moyen le plus utilisé pour injecter les dépendances dans Spring. Par défaut, l’annotation @Autowired résout les dépendances par type. Cela fonctionne bien jusqu’à ce que nous n’ayons qu’un seul bean avec le même type, mais le framework Spring lancera une exception si deux beans avec le même type sont disponibles dans le conteneur.
L’annotation @Autowired ne fonctionnera pas dans ce cas. Parce que Spring ne sait pas quels sont les beans qui ont besoin d’être “autowired”.

Dans Spring, il existe deux types “d’autowiring” :

  1. Autowiring par type
  2. Autowiring par nom

Autowiring par type : utilise le type de la classe pour “autowire” la classe du bean de Spring Boot. Le bean est autowired en fonction du type de la variable.
Autowiring par nom : le nom de la variable est utilisé pour l’injection de dépendance. Le nom de la variable doit être le même que le nom de la classe ou du bean configuré dans l’annotation @Component.

Prenons un exemple pour les deux :

public interface Shape {
    public void draw();
}

@Component
class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Invocation de l'instance du Rectangle");
    }
}

@Component
class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Invocation de l'instance Circle");
    }
}

Dans l’exemple ci-dessus, l’interface Shape est implémentée par deux classes Circle et Rectangle.
On peut donc dire que les deux sont des instances de Shape. Il a fait de Rectangle et Circle des beans de Spring en utilisant l’annotation @Component. Maintenant, voyons comment “autowire” ces beans dans une autre classe :

@Component
class ShapeService {

    @Autowired
    private Shape rectangle; // Par nom
    
    @Autowired
    private Rectangle myRectangle; // Par type
}

Ici, dans la classe ShapeService, la forme Rectangle est traitée automatiquement de deux manières :

  • Ici, dans le premier @Autowired, la variable rectangle est “autowired” sur la base du nom de la variable. Ici, lorsque Spring vérifie le type de la variable, il peut voir que c’est Shape. Mais il y a deux implémentations de forme : Rectangle et Circle. Donc Spring n’obtient pas une solution appropriée pour le composant qui doit être “autowire. Ensuite, Spring vérifie le nom de la variable (rectangle) et découvre qu’un composant Shape avec le même nom est disponible. Et oui, le composant Rectangle est disponible. Donc Spring va injecter la propriété avec le composant rectangle.
  • Dans le second @Autowired, le type de la propriété est Rectangle. Ainsi, Spring injecte directement le composant Rectangle à la propriété myRectangle.

Il ne s’agit pas seulement d’utiliser l’annotation @Autowired dans les propriétés. Il peut également utiliser l’annotation @Autowired sur les constructeurs, les setter et la création de bean.
Voyons maintenant les trois autres utilisations de l’annotation @Autowired.

 


 

L’annotation @Autowired sur les constructeurs

L’annotation @Autowired est utilisée dans la méthode du constructeur de la classe. La valeur de l’argument du constructeur est passée automatiquement lors de la création de la classe d’instance. Dans le démarrage de Spring, @Autowired attribue des valeurs au constructeur. C’est une alternative à l’utilisation de l’annotation @Autowired dans les propriétés :

@Component
class ShapeService {

    private Shape shape;
    
    private Shape shapeTwo;
    
    @Autowired
    public ShapeService (Shape circle, Rectangle shape2) {
        this.shape = circle; // Autowire sur constructeur. Autowire par nom
        this.shapeTwo = shape2; // Autowire sur constructeur. Autowire par type
    }
    
    public void invokeService() {
        shape.draw();
        shapeTwo.draw();
    }
}

L’exemple ci-dessus montre un exemple d’autowiring utilisant le constructeur. Ici, le paramètre du constructeur circle est autowired basé sur le nom et le paramètre shape2 autowired basé sur le type.


 

L’annotation @Autowired sur les Setters

Similaire à l’autowiring des propriétés. Il est capable d’implémenter l’annotation @Autowired sur les setters. L’exemple ci-dessous montre un exemple d’autowiring basé sur un setter :

@Component
class ShapeService {

    private Shape shape;
    
    private Shape shapeTwo;
    
    public Shape getShape() {
        return shape;
    }
    
    // Autowiring sur setter et par nom
    @Autowired
    public void setShape(Shape circle) {
        this.shape = circle;
    }
    
    public Shape getShapeTwo() {
        return shapeTwo;
    }
    
    // Autowiring sur setter et par type
    @Autowired
    public void setShapeTwo(Rectangle shapeTwo) {
        this.shapeTwo = shapeTwo;
    }
    
    public void invokeService() {
        shape.draw();
        shapeTwo.draw();
    }
}


Ici, dans la méthode setShape, l’autowiring est basé sur le nom du paramètre (composant Circle).
Dans la méthode setShapeTwo, l’autowiring est basé sur le type de paramètre (composant Rectangle).


 

@Qualifier

L’annotation @Qualifier aide à faire plus de contrôle sur l’annotation @Autowire.
@Qualifier aide à choisir le bon bean pour l’injection de dépendance.

Cette annotation est utilisée avec l’annotation @Autowired. Lorsque vous avez besoin de plus de contrôle sur le processus d’injection de dépendances, @Qualifier peut être utilisé.
@Qualifier peut être spécifié sur des arguments de constructeur individuels ou des paramètres de méthode. Cette annotation est utilisée pour éviter la confusion qui se produit lors de l’injection de nom par autowiring.

Voyons comment l’annotation @Qualifier résout les conflits d’autowiring :

public interface Shape {
    public void draw();
}

@Component
class Rectangle implements Shape {

    @Override
    public void draw() {
        System.out.println("Invocation de l'instance de Rectangle");
    }
}

@Component
class Circle implements Shape{

    @Override
    public void draw() {
        System.out.println("Invocation de l'instance de Circle");
    }
}


Dans l’exemple ci-dessus, vous pouvez voir qu’il existe deux implémentations de l’interface Shape, à savoir Circle et Rectangle. Les classes Circle et Rectangle sont déclarées avec l’annotation @Component, ce qui signifie qu’il s’agit de beans Spring.

@Component
class ShapeService {

    @Autowired
    private Shape Shape1;
    
    @Autowired
    private Shape Shape2;
    
    public void invokeService() {
        Shape1.draw();
        Shape2.draw();
    }
}

@Component
class ShapeService {

    @Autowired
    private Shape Shape1;
    
    @Autowired
    private Shape Shape2;
    
    public void invokeService() {
        Shape1.draw();
        Shape2.draw();
    }
}


Le code ci-dessus permet d’autowiring les composants Shape créés. Mais Spring ne peut pas trouver quel composant s’autowire à ces champs Shape1 et Shape2.
L’autowiring par type ou par nom ne fonctionne pas ici. Ainsi Spring jette l’exception :

Could not autowire. There is more than one bean of 'Shape' type.
Beans:
circle   (Shape.java) rectangle   (Shape.java) 


L’annotation @Qualifier aide à résoudre ce problème. @Qualifier peut être utilisée avec l’annotation @Autowired et servir à spécifier l’objet exact que l’on souhaite rendre autonome.

Dans le code ci-dessous, à l’aide de l’annotation @Qualifier, il est mentionné que le champ Shape1 s’autowire au composant Circle et que le champ Shape2 s’autowire au composant Rectangle :

@Component
public class ShapeService {

    @Autowired
    @Qualifier("circle")
    private Shape Shape1;
    
    @Autowired
    @Qualifier("rectangle")
    private Shape Shape2;
    
    public void invokeService() {
        Shape1.draw();
        Shape2.draw();
    }
}

 


 

@Configuration

L’annotation @Configuration est utilisée pour déclarer plus d’un bean dans une classe à l’aide de l’annotation @Bean. L’annotation @Configuration est une annotation “class-level” et normalement, dans un bean de configuration, elle contient une ou plusieurs méthodes @Bean et peut être traitée par Spring pour générer des définitions de bean et des requêtes de service pour ces beans au moment de l’exécution.

Utilisez l’annotation @Configuration au-dessus de n’importe quelle classe pour déclarer que cette classe fournit une ou plusieurs méthodes @Bean, et peut être traitée par Spring pour générer des beans et des requêtes de service pour ces beans au moment de l’exécution.

public interface Shape {
    public void draw();
}

class Circle implements Shape{

    @Override
    public void draw() {
        System.out.println("Invocation de l'instance de Circle");
    }
}

class Rectangle implements Shape {

    @Override
    public void draw() {
        System.out.println("Invocation de l'instance de Rectangle");
    }
}


Dans l’extrait de code ci-dessus, il est déclaré l’implémentation du Rectangle et du Cercle. Mais ces classes ne sont pas des beans car elles ne sont pas déclarées avec l’annotation @Component ou toute autre annotation liée à Spring. Les classes sont complètement libres de l’annotation Spring. En utilisant le code suivant, il est possible d’ajouter ces classes aux beans Spring sans toucher aux classes :

@Configuration
class MyConfiguration {

    @Bean
    public Shape rectangle() {
        return new Rectangle();
    }
    
    @Bean
    public Shape circle() {
        return new Circle();
    }
}


Dans l’extrait de code ci-dessus, Rectangle et Cercle ont été ajoutés comme faisant partie des beans de Spring. Ensuite, il faut “autowire” ces beans :

@Component
class ShapeService {

    @Autowired
    private Shape rectangle;
    
    @Autowired
    private Shape circle;
    
    public void invokeService() {
        rectangle.draw();
        circle.draw();
    }
}


Nous pouvons donc conclure que l’un des principaux avantages de l’annotation @Configuration est qu’elle permet de mettre en œuvre l’annotation @Bean.

 


 

@Bean

Cette annotation est utilisée au niveau de la méthode.
@Bean fonctionne avec @Configuration pour créer des beans Spring. Comme mentionné précédemment, @Configuration aura des méthodes pour instancier et configurer les dépendances. Ces méthodes seront annotées avec @Bean.
Nous avons déjà vu les exemples d’annotation @Bean dans le cadre des exemples d’annotation @Configuration.

 


 

@ComponentScan

Cette annotation est utilisée avec l’annotation @Configuration pour permettre à Spring de connaître les packages à analyser pour les composants annotés. L’annotation @ComponentScan est également utilisée pour spécifier les paquets de base à analyser à l’aide des attributs basePackageClasses ou basePackage.
Par défaut, l’annotation @ComponentScan recherche les composants dans le paquet courant et tous ses sous-paquets.

En utilisant cette annotation, il est possible de spécifier quels sont les paquets qui doivent être analysés.

 


 

@Lazy

Par défaut, le framework Spring initialise tous les beans singleton au démarrage de l’application et les place dans le contexte de l’application. Cependant, dans certains cas, nous devons créer des beans chaque fois que cela est nécessaire, mais pas au moment du démarrage de l’application ou de l’initialisation du contexte d’application.
Dans Spring, nous pouvons réaliser cela en utilisant l’annotation @Lazy.

Dans le code ci-dessous, on ajoute l’annotation @Lazy pour le composant LazyClass :

public class LazyClass {
    public LazyClass() {
        System.out.println("Initialisation LazyClass");
    }
}

class NormalClass {
    public NormalClass() {
        System.out.println("Initialisation NormalClass");
    }
}

@Configuration
public class LazyConfiguration {

    @Bean
    public NormalClass normalClass() {
        return new NormalClass();
    }
    
    @Lazy
    @Bean
    public LazyClass lazyClass() {
        return new LazyClass();
    }
}


Après le démarrage du serveur, vous pouvez voir que le code du constructeur de LazyClass n’est pas affiché dans le code de démarrage du serveur, ce qui signifie qu’il n’est pas initialisé au démarrage :

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.1)
2021-06-18 15:18:46.920  INFO 13240 --- [           main] com.example.tests.TestsApplication       : Starting TestsApplication using Java 1.8.0_281 on DESKTOP-058URK9 with PID 13240 (D:\Dev\Projets\tests\target\classes started by ng738 in D:\Dev\Projets\tests)
2021-06-18 15:18:46.931  INFO 13240 --- [           main] com.example.tests.TestsApplication       : No active profile set, falling back to default profiles: default
2021-06-18 15:18:47.653  INFO 13240 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-06-18 15:18:47.659  INFO 13240 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-06-18 15:18:47.660  INFO 13240 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.46]
2021-06-18 15:18:47.721  INFO 13240 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-06-18 15:18:47.721  INFO 13240 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 752 ms
Initialisation NormalClass
2021-06-18 15:18:48.047  INFO 13240 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-06-18 15:18:48.061  INFO 13240 --- [           main] com.example.tests.TestsApplication       : Started TestsApplication in 1.446 seconds (JVM running for 2.517)


Dans le code ci-dessous, la méthode runner est exécutée après le démarrage du serveur. Ainsi, il essaie d’accéder au bean LazyClass à partir de l’ApplicationContext après le démarrage du serveur. Lorsque le système appelle contexte.getBean(LazyClass.class), il est le seul à initialiser le bean LazyClass :

@Configuration
public class Configuration {

    @Bean
    CommandLineRunner runner() {
        return args -> {
            System.out.println("---- Initialisation Application Context ----");
            AnnotationConfigApplicationContext contexte = new AnnotationConfigApplicationContext(Configuration.class);
            System.out.println("---- Appel vers le bean LazyClass ----");
            contexte.getBean(LazyClass.class);
        };
    }
}

Voici l’affichage console au lancement de l’application avec les appels :

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.5.1)
2021-06-18 15:23:23.085  INFO 9000 --- [           main] com.example.tests.TestsApplication       : Starting TestsApplication using Java 1.8.0_281 on DESKTOP-058URK9 with PID 9000 (D:\Dev\Projets\tests\target\classes started by ng738 in D:\Dev\Projets\tests)
2021-06-18 15:23:23.089  INFO 9000 --- [           main] com.example.tests.TestsApplication       : No active profile set, falling back to default profiles: default
2021-06-18 15:23:23.870  INFO 9000 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-06-18 15:23:23.875  INFO 9000 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-06-18 15:23:23.876  INFO 9000 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.46]
2021-06-18 15:23:23.922  INFO 9000 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-06-18 15:23:23.923  INFO 9000 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 773 ms
Initialisation NormalClass
2021-06-18 15:23:24.135  INFO 9000 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-06-18 15:23:24.141  INFO 9000 --- [           main] com.example.tests.TestsApplication       : Started TestsApplication in 1.515 seconds (JVM running for 2.255)
---- Initialisation Application Context ----
---- Appel vers le bean LazyClass ----
2021-06-18 15:23:24.147  INFO 9000 --- [           main] ConditionEvaluationReportLoggingListener : 


De plus, il est capable d’appliquer l’annotation @Lazy au dessus de la classe de configuration afin que tous les beans à l’intérieur de la classe de configuration soient initialisés :

@Lazy
@Configuration
public class LazyConfiguration {

    @Bean
    public NormalClass normalClass() {
        return new NormalClass();
    }
    
    @Bean
    public LazyClass lazyClass() {
        return new LazyClass();
    }
}

@Lazy peut s’appliquer à la classe annotée @Component :

@Lazy
@Component
  public class LazyClass {
     public LazyClass() {
            System.out.println("---- Initialisation LazyClass");
    }
}


Lorsque l’annotation @Lazy est présente avec l’annotation @Component au niveau de la classe, le composant ne sera pas créé par le conteneur, mais lors de la première demande du composant. En même temps que le point d’injection @Autowired, vous devez le marquer avec l’annotation @Lazy.

Il faudrait que le composant ainsi que le champ d’autowiring soient mentionnés avec l’annotation @Lazy. Ensuite, seul le composant sera chargé “lazy”. Explication :

@Lazy
@Component
public class LazyClass {

       public LazyClass() {
            System.out.println("---- Initialisation LazyClass ----");
        }
        
       public void hello() {
            System.out.println("Hello !");
        }
    }
    
@Configuration
public class Configuration {

     @Lazy
     @Autowired
     private LazyClass lazyClass;
}

 


 

@Value

Cette annotation est utilisée au niveau du champ, du paramètre du constructeur et du paramètre de la méthode. L’annotation @Value indique une expression de valeur par défaut pour le champ ou le paramètre afin d’initialiser la propriété avec.
Comme l’annotation @Autowired indique à Spring d’injecter un objet dans un autre lorsqu’il charge le contexte d’application, vous pouvez également utiliser l’annotation @Value pour injecter les valeurs d’un fichier de propriétés dans l’attribut d’un bean. Elle prend en charge les caractères de remplacement #{…} et ${…}.

L’exemple ci-dessous montre la lecture de différentes valeurs à partir d’un fichier de propriétés, “applications.properties” :

unicorn.value = Bonjour la licorne
unicorn.property = Bonjour la propriété
unicorn.array = Premier, Second, Troisième
unicorn.rank = 10
unicorn.values.underscore = A_B_C
unicorn.nicolas.details = {name: "Nicolas", age: "28", country: "France"}


Le code ci-dessous explique comment obtenir les propriétés du fichier ci-dessus de différentes manières :

@Component
public class TestComponent {

    // Possibilité de mentionner directement la valeur à l'intérieur de l'annotation @Value
    @Value("Bonjour la licorne")
    private String value;

    // La valeur est lue dans le fichier application.properties
    @Value("${unicorn.value}")
    private String value1;

    /*
    La valeur de la propriété unicorn.property est lue dans le
    fichier application.properties. Si il n'existe aucune propriété
    de renseignée dans le fichier, alors la valeur par défaut sera
    "OtherProperty"
     */
    @Value("${unicorn.property:OtherProperty")
    private String property;
    /*
    Lis les valeurs séparées par des virgules et
    les converties en liste
     */
    @Value("${unicorn.array}")
    private List<String> listRank;

    // Lis l'entier dans l'application.properties
    @Value("${unicorn.rank}")
    private Integer myRank;

    /**
     * Lis la valeur du fichier de propriété et split avec un "_"
     */
    @Value("#{'${unicorn.values.underscord}'.split('_')}")
    private List<String> separatedValues;

    // Lis les valeurs dans le fichier de propriété et assigne dans une map
    @Value("#{${unicorn.nicolas.details}}")
    private Map<String, String> myProfile;

    // Assigne dans l'integer l'age cotenue dans le fichier de propriété
    @Value("#{${unicorn.nicolas.details}.age}")
    private Integer myAge;
}

 

 

1 réflexion sur “Les annotations de base du Framework Spring”

  1. Ping : Explication de l’héritage en Java

Laisser un commentaire

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

Retour en haut
%d blogueurs aiment cette page :