L’apport du framework Spring

L'objet de ce billet est d'expliciter l'apport du framework Spring dans nos développements.

Les concepts de Spring IoC sont présentés accompagnés d'exemple pratique.

L'idée est de partir des critères qualifiant un code de "mauvais" puis d'indiquer les moyens de le corriger ou le contourner.

On s’appuie sur le framework Spring pour améliorer le code et ceci de manière non intrusive.

Le concept central dans le framework Spring est:
"le cycle de vie d'un composant, son assemblage et la gestion de ses dépendances doivent se faire en dehors du composant lui-même"
Il découle que le code de "plomberie" pour gérer les instances de classes (beans) ne sont plus à faire par le développeur.
Une connaissance sommaire de Spring facilite la lecture de ce billet.

Dans l'exemple pratique de ce billet, on recourt aux annotations pour l'injection des dépendances entre les instances de classes (beans).

Question: Quels sont les critères d'un mauvais code?

De manière conceptuelle, les critères d'un mauvais code (ou mauvaise conception) sont au nombre de trois (retenons R.F.I ):

1- Un code Rigide
2- Un code Fragile
3- Un code Immobile

Un code rigide est un code qui est difficile de modifier car toute modification impacte toutes les parties (couches) de l'application.

Un code fragile c'est un code où la simple modification d'une partie (couche) de l'application a pour conséquence de casser d'autres parties (couches) .

Un code Immobile c'est un code non réutilisable ailleurs car il est tellement lié à l'application.

Voici un premier code simple mettant en évidence la présence de l'un de ces trois critères (RFI):

 
/*classe dao*/
2. public class Dao implements IDao{
3.   public String getInfo(){
    //.... code 
4.    return "dao"
    }

/*interface IService*/
5. public interface IService { getInfo();}

/*classe Service*/
6. public class Service implements IService{
7.    private Dao dao;
8.    public String getInfo(){
9.     return dao.getInfo();
       }
       //... etc     
}

A la ligne 7 la couche service dépend d'un détail d'implémentation car son attribut 'dao' pointe sur la classe d'implémentation. Ce qui rend le code rigide et fragile.

Pour contourner ceci, dans la classe Service, l'attribut doit pointer sur l'interface IDao à créer.
Mais c'est insuffisant car à un moment ou un autre l'attribut 'dao' doit être instancié dans le service.

Or l'opérateur 'new' d'instanciation doit porter sur une implémentation. Ainsi, le service est toujours lié au détail d'implémentation.
Nous souhaitons que la couche service n'a aucune référence sur les implémentations.

Pour corriger ces facteurs, deux principes sont à respecter (principes de dépendances ):

P1. Les modules de haut niveau ne devrait pas dépendre de modules de bas niveau, les deux doivent dépendre des couches abstraites
P2. Les parties abstraites ne devraient pas dépendre des détails (d'implémentation), ces détails doivent dépendre uniquement des abstractions.

Spring avec le concept IoC va nous permettre de contourner complètement les facteurs RFI.
Nous allons procéder à la mise en place en reprenant l'exemple précédent.

 
/*interface IDao*/
public interface IDao{ String getInfo();}

/*classe dao*/
@Repository
public class Dao implements IDao{
public String getInfo(){
    //.... code 
      return "<dao/>"
 }
/*interface IService*/
public interface IService { getInfo();}

/*classe Service*/
@Service
public class Service implements IService{
     @Autowired
     private IDao dao; /**** ligne modifiée ****/
     public String getInfo(){
       return dao.getInfo();
    }     
}

Notez la présences des annotations @Repository et @Service de Spring ajoutées dans les classes d'implémentation.
L'autre modification consiste à injecter, via l'annotation @Autowired de Spring, une instance de 'dao'.
Toutes ces annotations servent à configurer Spring. On peut bien évidemment obtenir le même résultat par de la configuration XML.
Par conséquence, la classe de détail Service ne dépend que de l'interface IDao.
Ainsi, nous obtenons une classe testable sans connaitre les détails d'implémentation.
On peut, par exemple, la tester juste avec un bouchon (mock) de la classe Dao.
En résumé, le couplage entre la couche service et celle de dao est faible.
Le framework Spring sera configuré afin de gérer les beans, les dépendances entre les beans et leur cycle de vie.
La figure suivante illustre cela springIoC.png
Pour ce faire, voici le fichier de configuration de spring qui vient en complément des annotations précédentes:

 
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 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"
 default-autowire="byName" default-lazy-init="true">
   <context:component-scan base-package="fr.netapsys.tb"/>
</beans>

Bien sûr, le fichier est maigre et il le restera surtout que l'on utilise que les annotations.
la ligne context:component-scan précise les packages où Spring doit chercher les beans à injecter.
Mais cela n'est pas suffisant comme nous le verrons ci-après.

Question: Que signifie un bean?
C'est une instance de classe simple (POJO) managée par Spring. C'est à dire, une classe instanciée et assemblée par Spring,

Question: A ce stade et juste en lisant le fichier de configuration de spring, quels sont les (nos) beans managés par Spring?

Il y a deux beans : 'dao' et 'service' puisque les deux classes sont correctement annotées (@Repository et @Service)
et surtout que les deux classes se trouvent dans le package du base-package du scan: 'fr.netapsys.tb'.

Attention, l'absence d'annotation appropriée sur une classe ou lorsque le base-package du scan n'englobe pas cette classe produisent une exception générée par Spring.

Enfin, voici les étapes de crétaion dans eclipse d'un projet simple permettant de pratiquer les concepts évoqués:

1- Créer un projet java standard (java 5 ou +)

2- Ajouter les jars suivants:

commons-logging,log4j,spring-beans,spring-core,spring-context

3- Ajouter au projet les packages:

    fr.netapsys.tb.dao,  fr.netapsys.tb.services

4- Ajouter le fichier Spring

  Nommez-le spring-context.xml. Il contient donc une ligne:
<context:component-scan base-package="fr.netapsys.tb "/>

5- Ajouter l'interface IDao et son implémentation Dao:

  L'interface déclare l'unique méthode: String getInfo(); 
L'implémentation ne fait que retourner la chaîne "<dao/>".
Ces deux classes sont dans le package fr.netapsys.tb.dao

6- Ajouter la classe IMyService et son implémentation MyService:

  L'interface IMyService déclare une méthode : String info();
MyService a un attribut de type IDao :

   public class MyService implements IMyService{
     private IDao dao;
    public String info(){ return dao.getInfos() ;}
 }  

@Service
   public class MyService implements IMyService{
     @Resource private IDao dao;
    public String info(){ return dao.getInfos() ;}
 }

7. Vérifier que vous annotez bien les classes Dao et Service. Sans ces annotations, Spring ne pourra pas voir ces beans dans le package de scan

8- Pour tester, ajoutez une classe Main puis insérez ces deux lignes dans sa méthode statique main :

ApplicationContext appCtx= new ClassPathXmlApplicationContext("spring-context.xml");

IMyService service=(IMyService) appCtx.getBean("myService");

La classe Main est dans le package fr.netapsys.tb.client

Voilà, vous avez donc tous les éléments pour tester par vous même l'intégration de spring et surtout découvrir son réel apport.

Dans la seconde partie de ce billet nous répondons à :

- Que doit-on utiliser BeanFactory ou ApplicationContext?
- Que doit-on préféter les annotations ou la configuration par XML?

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.