• 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

Une injection de dépendances plus sophistiquée

Vous avez compris le principe de l'inversion de contrôle (IoC) et l'avantage de faire de l'injection de dépendances (DI).

Faire cela à la main peut vite devenir fastidieux et source d'erreur. Un oubli est vite arrivé !

L'injection de dépendances avec une approche séquentielle

Actuellement, dans l'application web, toute l'injection de dépendances est « pilotée » par la classe DependencyInjectionListener. L'approche adoptée est du genre :

  1. Je crée toutes les instances des Managers.

  2. Je crée l'instance de la ManagerFactory.

  3. J'injecte toutes les instances des Managers dans la ManagerFactory via des setters.

  4. J'injecte l'instance de la ManagerFactory dans les classes qui en ont besoin (ici AbastractResource).

  5. Et on fait de même avec les DAO et la DaoFactory, etc.

Avec cette approche, je peux rencontrer quelques soucis. Par exemple, imaginez que ProjetDao ait besoin de UtilisateurDao pour pouvoir charger l'utilisateur qui a créé le projet quand il va récupérer ce dernier en base de données. Il faudrait alors :

  • soit injecter la DaoFactory dans ProjetDao ;

  • soit lui injecter directement l'UtilisateurDao.

Cela impose donc de bien ordonner les instanciations et les injections. Et ça, ça commence à complexifier la tâche de mon « injection de dépendances maison » !

Mais, rassurez-vous, je ne vais pas vous laisser comme ça, une autre manière de faire est possible...

Utiliser un « IoC container »

Spring apporte une solution visant à simplifier l'injection de dépendances en changeant un petit peu d'approche. Il utilise un « conteneur d’inversion de contrôle », IoC container en anglais.

L'idée est que :

  1. chaque classe entrant en jeu dans l'injection de dépendances se déclare dans l'IoC container (AbstractResource, ManagerFactoryImpl, ProjetManagerImpl, ProjetDaoImpl...) ;

  2. pour chaque objet, on indique les dépendances à injecter (ex.: ManagerFactoryImpl a besoin d'un ProjetManager, d'un TicketManager...) ;

  3. l'IoC container se charge d'instancier et d'injecter les dépendances afin de produire la grappe d'objets pleinement configurée.

C'est plutôt cool, non ?

Voici à quoi peut ressembler la configuration de l'IoC container de Spring IoC :

<beans>
    <!-- ===== Déclaration des DAO ===== -->
    <bean id="projetDao" class="org.example.demo.ticket.consumer.impl.dao.ProjetDaoImpl">
        <property name="utilisateurDao" ref="utilisateurDao" />
    </bean>

    <bean id="utilisateurDao" class="org.example.demo.ticket.consumer.impl.dao.UtilisateurDaoImpl" />


    <!-- ===== DaoFactory ===== -->
    <bean id="daoFactory" class="org.example.demo.ticket.consumer.impl.dao.DaoFactoryImpl">
        <property name="projetDao" ref="projetDao" />
        <property name="utilisateurDao" ref="utilisateurDao" />
    </bean>

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

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

Même si c'est assez parlant, voici quelques explications :

  • On déclare les beans (élément bean). Chaque bean a :

    • un identifiant (attribut id),

    • et on précise quelle est la classe d'implémentation (attribut class).

  • Pour chaque bean on indique quels sont les attributs de l'objet à injecter (élément property) :

    • en précisant le nom de l'attribut (attribut name),

    • l'identifiant du bean à injecter dedans (attribut ref).

Je ne vois pas bien la différence et l'utilité de l'IoC container ?

La différence réside dans le fait que l'injection de dépendances adopte une approche déclarative au lieu d'une approche séquencielle d'instanciations et d'injections. On n'est alors plus soumis à un ordre précis de création d'objets pour pouvoir gérer l'injection, et toute la configuration de l'injection pour une classe se trouve à un seul endroit.

Ceci permet également d'adresser de manière transparente les dépendances cycliques — même si elles devraient rester rares.

Imaginez que :

  • ProjetDaoImpl ait besoin d'un TicketDao et d'un UtilisateurDao ;

  • TicketDaoImpl ait besoin d'un ProjetDaoImpl et d'un UtilisateurDao ;

Avec une approche séquentielle (sans IoC container)

Avec mon système d'injection « à la main », cela donnerait :

UtilisateurDaoImpl vUtilisateurDaoImpl = new UtilisateurDaoImpl();

ProjetDaoImpl vProjetDaoImpl = new ProjetDaoImpl();
vProjetDaoImpl.setUtilisateurDao(vUtilisateurDaoImpl);
// La deuxième injection n'est pas encore réalisable
// (vTicketDaoImpl n'existe pas encore)
// vProjetDaoImpl.setTicketDao(vTicketDaoImpl);

TicketDaoImpl vTicketDaoImpl = new TicketDaoImpl();
vTicketDaoImpl.setProjetDao(vProjetDaoImpl);
vTicketDaoImpl.setUtilisateurDao(vUtilisateurDaoImpl);

// Maintenant je peux injecter le TicketDao dans le ProjetDaoImpl
vProjetDaoImpl.setTicketDao(vTicketDaoImpl);

// ...

On pourrait réordonner les choses en faisant d'abord toutes les instanciations, puis toutes les injections :

// ===== Instanciations =====
UtilisateurDaoImpl vUtilisateurDaoImpl = new UtilisateurDaoImpl();
ProjetDaoImpl vProjetDaoImpl = new ProjetDaoImpl();
TicketDaoImpl vTicketDaoImpl = new TicketDaoImpl();
// ...

// ===== Injections =====
vProjetDaoImpl.setUtilisateurDao(vUtilisateurDaoImpl);
vProjetDaoImpl.setTicketDao(vTicketDaoImpl);
vTicketDaoImpl.setProjetDao(vProjetDaoImpl);
vTicketDaoImpl.setUtilisateurDao(vUtilisateurDaoImpl);
// ...

Mais alors le risque d'oublier une injection est plus grand car l'initialisation d'un objet est répartie sur 2 blocs distincts.

Avec une approche déclarative (avec IoC container)

Avec une approche déclarative, je ne me soucie plus de l'ordre, je ne fais que déclarer les éléments :

<beans>
    <bean id="projetDao" class="org.example.demo.ticket.consumer.impl.dao.ProjetDaoImpl">
        <property name="ticketDao" ref="ticketDao" />
        <property name="utilisateurDao" ref="utilisateurDao" />
    </bean>

    <bean id="ticketDao" class="org.example.demo.ticket.consumer.impl.dao.TicketDaoImpl">
        <property name="projetDao" ref="projetDao" />
        <property name="utilisateurDao" ref="utilisateurDao" />
    </bean>

    <bean id="utilisateurDao" class="org.example.demo.ticket.consumer.impl.dao.UtilisateurDaoImpl" />
</beans>

Et cette approche déclarative prend tout son sens si on utilise directement les annotations de la JSR-250 et JSR-330 dans le code source à la place d'un fichier de configuration :

@ManagedBean
public class ProjetDaoImpl implements ProjetDao {
    @Inject
    private TicketDao ticketDao;

    @Inject
    private UtilisateurDao utilisateurDao;

    // ...
}


@ManagedBean
public class TicketDaoImpl implements TicketDao {
    @Inject
    private ProjetDao projetDao;

    @Inject
    private UtilisateurDao utilisateurDao;

    // ...
}


@ManagedBean
public class UtilisateurDaoImpl implements UtilisateurDao {
    // ...
}

OK, ça fait peut-être beaucoup de choses d'un coup. Ce que vous devez retenir et bien comprendre, c'est le principe de l'Ioc container et de l'approche déclarative.

Ne vous souciez pas de retenir la syntaxe de configuration, c'est justement ce que je vais détailler pas à pas dans le prochain chapitre. Vous allez enfin utiliser Spring !

Example of certificate of achievement
Example of certificate of achievement