• 15 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 12/15/20

L'inversion de contrôle avec Spring

Mettre en place Spring IOC

Ajouter les dépendances Maven de Spring

La première chose à faire pour utiliser Spring c'est d'ajouter les dépendances requises dans le projet Maven.

  1. Je commence par ajouter la définition des dépendances dans la section dependencyManagement du projet racine (fichier ticket/pom.xml) :

    <project>
        ...
        <properties>
            ...
            <spring.version>4.3.11.RELEASE</spring.version>
        </properties>
        <dependencyManagement>
            <dependencies>
                ...
                <dependency>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-framework-bom</artifactId>
                    <version>${spring.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
        ...
    </project>
  2. Dans le module ticket-technical, j'ajoute la dépendance vers Spring Context :

    <project>
        ...
        <dependencies>
            ...
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
            </dependency>
        </dependencies>
        ...
    </project>

Créer un premier fichier de configuration

Avec Spring, il y a plusieurs moyens de configurer l'IoC Container :

  • avec un fichier XML ;

  • en utilisant les annotations des JSR-250 et JSR-330 ;

  • en utilisant les annotations spécifiques à Spring ;

  • via le code de l'application ;

  • et même en mixant plusieurs de ces moyens !

Pour l'instant, je vais me concentrer uniquement sur l'utilisation d'un fichier XML.

Je crée un fichier applicationContext.xml dans le répertoire src/main/resources du module ticket-webapp et y ajoute la configuration de l'injection de dépendances :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- ===== Déclaration des Managers ===== -->
    <bean id="projetManager" class="org.example.demo.ticket.business.impl.manager.ProjetManagerImpl"/>
    <bean id="ticketManager" class="org.example.demo.ticket.business.impl.manager.TicketManagerImpl"/>

    <!-- ===== ManagerFactory ===== -->
    <bean id="managerFactory" class="org.example.demo.ticket.business.ManagerFactory">
        <property name="projetManager" ref="projetManager"/>
        <property name="ticketManager" ref="ticketManager"/>
    </bean>

    <!-- ==== AbstractResource ===== -->
    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="targetClass" value="org.example.demo.ticket.webapp.rest.resource.AbstractResource"/>
        <property name="targetMethod" value="setManagerFactory"/>
        <property name="arguments" ref="managerFactory"/>
    </bean>
</beans>

Initialiser Spring dans l'application web

Il reste une dernière étape : initialiser l'IoC container de Spring dans l'application web en remplaçant l'actuel listener DependencyInjectionListener par celui fourni par Spring (module spring-web).

  1. Ajoutez la dépendance vers spring-web dans le POM du module ticket-webapp :

    <project>
        ...
        <dependencies>
            ...
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-web</artifactId>
            </dependency>
        </dependencies>
    </project>
  2. Ajoutez le listener de Spring et sa configuration dans le fichier src/main/webapp/WEB-INF/web.xml :

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
             version="3.1">
        <!-- ... -->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:/applicationContext.xml</param-value>
        </context-param>
    </web-app>
  3. Supprimez l'ancien listener dans le fichier src/main/webapp/WEB-INF/web.xml (org.example.demo.ticket.webapp.listener.DependencyInjectionListener).

  4. Supprimez la classe org.example.demo.ticket.webapp.listener.DependencyInjectionListener.

Et voilà, il n'y a rien d'autre à faire, Spring fait tout le reste. ;)

Initialiser Spring dans les batches

Pour le module ticket-batch, il faut créer également un fichier applicationContext.xml dans le répertoire src/main/resources du module :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- ===== Déclaration des Managers ===== -->
    <bean id="projetManager" class="org.example.demo.ticket.business.impl.manager.ProjetManagerImpl"/>
    <bean id="ticketManager" class="org.example.demo.ticket.business.impl.manager.TicketManagerImpl"/>

    <!-- ===== ManagerFactory ===== -->
    <bean id="managerFactory" class="org.example.demo.ticket.business.ManagerFactory">
        <property name="projetManager" ref="projetManager"/>
        <property name="ticketManager" ref="ticketManager"/>
    </bean>

    ...
</beans>

Et dans la classe la méthode main, il faut charger l'application context Spring à partir du fichier XML :

package org.example.demo.ticket.batch;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void main(String[] pArgs) throws TechnicalException {
        ApplicationContext vApplicationContext
            = new ClassPathXmlApplicationContext("classpath:/applicationContext.xml");

        // Il est possible de récupérer un bean dans ce contexte :
        ManagerFactory vManagerFactory
            = vApplicationContext.getBean("managerFactory", ManagerFactory.class);

        // suite de l'implémentation des batches...
    }
}

Configurer l'IoC avec des annotations

Comme je le disais plus tôt, il y a plusieurs moyens de configurer l'IoC container de Spring. À la place ou en complément de fichiers XML, vous pouvez aussi utiliser des annotations.

Avec les annotations, la définition des besoins en dépendances est au plus près du code.

  Avec le fichier XML, la liste des ingrédients de la tartiflette est notée dans un livre de cuisine. Avec les annotations, cette liste est notée directement au fond du plat à gratin !

Au niveau des annotations vous avez le choix :

  • soit utiliser celles des JSR-250 et JSR-330 ;

  • soit utiliser les annotations spécifiques à Spring ;

  • soit mixer les deux.

Les annotations spécifiques à Spring proposent plus de fonctionnalités que celles des JSR-250 et JSR-330, mais vous créez alors une dépendance forte entre le code de votre application et l'API du framework Spring.

Pour pouvoir utiliser les annotations des JSR-250 et/ou JSR-330 vous devez ajouter les dépendances vers ces API dans Maven :

<!-- JSR-250 -->
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.2</version>
</dependency>
<!-- JSR-330 -->
<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

Je vais maintenant vous montrer comment utiliser les annotations. Je ne vais pas passer sur toutes les annotations, mais seulement les plus importantes, vous pourrez toujours consulter la documentation pour en savoir plus.

Il faut distinguer 3 parties dans la configuration :

  • un bean qui déclare ses dépendances, c'est-à-dire les éléments qu'il faut lui injecter

  • un bean qui se déclare auprès de l'IoC container pour indiquer qu'il peut être injecté dans d'autres beans

  • l'initialisation de l'IoC container (le bootstrapping)

Définir ses dépendances

Quand un élément doit être la cible d'une injection de dépendances, il suffit de l'annoter avec @Inject (JSR-330) ou @Autowired (Spring).

import javax.inject.Inject;
// ...

public class ManagerFactoryImpl implements ManagerFactory {

    @Inject
    private ProjetManager projetManager;

    @Inject
    private TicketManager ticketManager;

    // ...
}

Les annotations @Inject et @Autowired peuvent être positionnées :

  • sur un attribut, comme ci-dessus

  • sur le setter d'un attribut

  • sur une méthode ayant un ou plusieurs paramètres (ce sont ces paramètres qui seront injectés)

  • sur un constructeur ayant un ou plusieurs paramètres (ce sont ces paramètres qui seront injectés)

Se déclarer en tant que bean

Afin que les classes se signalent en tant que bean injectable, il suffit de les annoter avec @ManagedBean (JSR-250), @Named (JSR-330) ou @Component (Spring).

import javax.inject.Named;
// ...

@Named
public class ProjetManagerImpl implements ProjetManager {
    //...
}

Il est possible de définir l'identifiant du bean dans l'annotation. Par exemple :

import javax.inject.Named;
// ...

@Named("projetManager")
public class ProjetManagerImpl implements ProjetManager {
    //...
}

Initialiser l'IoC container

Pour initialiser le contexte vous avez plusieurs solutions. Je vais vous en proposer 2 qui permettent de combiner les configurations à base de fichiers XML et d'annotations.

Dans tous les cas, dans le fichier XML applicationContext.xml, il faut supprimer tout ce qui est maintenant géré par les annotations (déclaration du bean projetManager, injection du bean projetManager dans le bean managerFactory...).

Ajout de contexte dans la configuration XML

Avec cette première solution, vous conservez l'initialisation actuelle qui charge le fichier XML applicationContext.xml.

Vous ajoutez simplement dans ce fichier les éléments suivants :

  • <context:annotation-config/> : prend en compte la configuration des injections (@Inject@Autowired...)

  • <context:component-scan base-package="..."/> : scanne les packages pour trouver les beans qui se déclarent (@Named@Component...)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns="http://www.springframework.org/schema/beans"
    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">

    <!-- Prend en compte la configuration des injections (@Inject...) -->
    <context:annotation-config/>

    <!-- Scanne les packages pour trouver les beans qui se déclarent (@Named...) -->
    <context:component-scan base-package="org.example.demo.ticket"/>

    <!-- ... -->
</beans>
Création d'une classe Java de configuration

La deuxième solution consiste à créer une classe Java de configuration utilisant des annotations spécifiques à Spring :

package org.example.demo.ticket.webapp.bootstrap;

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

@Configuration
@ComponentScan("org.example.demo.ticket")
@ImportResource("classpath:/applicationContext.xml")
public class SpringConfiguration {

}
  • @Configuration : indique que cette classe propose des éléments de configuration Spring

  • @ComponentScan("org.example.demo.ticket") : indique qu'il faut scanner le package org.example.demo.ticket et ses sous-packages à la recherche de beans se déclarant

  • @ImportResource("classpath:/applicationContext.xml") : il est toujours possible d'ajouter des éléments de configuration dans des fichiers XML. Cette annotation permet d'importer ces fichiers XML.

Au niveau du bootstrapping, il faut maintenant utiliser cette nouvelle classe comme point d'entrée de la configuration et non plus le fichier applicationContext.xml.

Pour l'application web, c'est dans le fichier srv/main/webapp/WEF-INF/web.xml que cela se passe :

  • le context-paramcontextConfigLocation indique maintenant la classe de configuration au lieu du fichier XML ;

  • ajout du context-paramcontextClass indiquant à Spring d'utiliser la classe AnnotationConfigWebApplicationContext pour charger l'application context Spring au lieu de la classe par défaut (XmlWebApplicationContext).

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <display-name>Ticket</display-name>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>org.example.demo.ticket.webapp.bootstrap.SpringConfiguration</param-value>
    </context-param>
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </context-param>
</web-app>

Pour le module ticket-batch, il faut utiliser le même principe :

  • créer une classe de configuration Spring : SpringConfiguration ;

  • utiliser la classe AnnotationConfigApplicationContext au lieu de la classe ClassPathXmlApplicationContext pour charger l'application context Spring.

package org.example.demo.ticket.batch;

import org.example.demo.ticket.batch.bootstrap.SpringConfiguration;
import org.example.demo.ticket.business.ManagerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {

    public static void main(String[] pArgs) throws TechnicalException {
        ApplicationContext vApplicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);

        ManagerFactory vManagerFactory = vApplicationContext.getBean("managerFactoryImpl", ManagerFactory.class);
        // ...
    }
}

Annotation vs XML

Pour résumer, la configuration uniquement en XML est assez verbeuse, mais elle n'introduit aucun couplage des objets manipulés, tant à Spring, qu'au mécanisme d'injection de dépendances (les objets ne savent d'ailleurs même pas qu'ils font l'objet d'une injection de dépendances automatisée). La configuration est centralisée mais sa séparation du code peut aussi être source d'erreur en cas d'oubli.

D'un autre côté, avec les annotations, la configuration est au plus près du code, les risques d'oublis sont moins importants, mais la configuration est disséminée dans toute l'application. De plus, le code de votre application se retrouve couplé à l'API Java EE (JSR-250 et JSR-330) ou celle du framework Spring. Les annotations sont plus invasives.

Enfin, il est possible de tirer les avantages des deux types de configuration en les mixant. Cependant, si vous faites cela, je ne saurais trop vous conseiller de le faire de manière cohérente et consistante dans toute l'application, mais également de documenter votre manière de faire. Mettez au moins un commentaire dans votre fichier XML indiquant qu'une partie de la configuration se fait via des annotations et si possible vos règles disant quand vous utilisez une annotation et quand vous utilisez le XML.

Chaque approche a ses avantages et inconvénients. À vous de choisir en fonction de vos priorités et de vos préférences.

Dans le chapitre suivant, je vous donnerai des conseils pour mieux utiliser Spring et vous montrerai qu'il est possible d'aller un peu plus loin en faisant de l'injection de configuration par exemple.

Example of certificate of achievement
Example of certificate of achievement