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 
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?
Commentaires