I. Présentation de Spring▲
Don't call us, we'll call you
I-A. Un rapide historique▲
Il y a quelques années, une poignée d'irréductibles supporters des concepts objets se sont élevés contre la lourdeur induite par les normes de la plateforme Java : le modèle de programmation et d'exécution des EJB furent alors choisis comme cible privilégiée de ces groupuscules alors disséminés un petit peu partout sur Internet. Spring fut alors implicitement élu par la communauté de développeurs : ce framework d'apparence simple, semble répondre aux besoins de leurs projets, ainsi qu'aux processus de développement les plus en vue (développement piloté par les tests, …).
I-B. Les modules Spring▲
L'organisation de Spring est modulaire. Pourvu d'un module de base « Spring core », six modules d'inégales importances apparaissent (en gras les modules majeurs) :
- Spring Core : module de gestion des dépendances entre beans (implémente l'injection de dépendances) ;
- Spring AOP : réservé à des développements très spécifiques ;
- Spring ORM : classes utilitaires permettant une intégration intéressante des différents framework de mapping O/R, notamment avec Hibernate ;
- Spring DAO : classes utilitaires facilitant à l'extrême le développement d'une couche d'accès aux données en JDBC pur ;
- Spring Context : permet de masquer une grande partie de la technologie nécessaire pour se connecter à des EJB, à JNDI, à JMS,… Mais aussi l'internationalisation de nos applications ;
- Spring Web : comment utiliser Spring depuis une application web ;
- Spring MVC : implémenter une application web en respectant le design pattern MVC (concurrent de Struts).
II. L'injection de dépendances▲
La compréhension de l'injection de dépendances est incontournable pour faire du Spring. Dérivé du concept d'« Inversion Of Control », son utilisation est relativement simple, l'utilisation à bon escient est quant à elle plus délicate.
II-A. Les dépendances▲
L'inversion de contrôle imaginée par Martin Fowler il y a plusieurs années maintenant a été reprise par Spring sous une forme particulière : l'injection de dépendances. L'injection de dépendances est un design pattern finalement très simple, simplicité ouvrant néanmoins des portes particulièrement intéressantes. La collaboration des objets les uns avec les autres est à la base des développements objets. Cette collaboration est rendue possible par la connaissance qu'un objet a d'un autre. Si un objet A a besoin de collaborer avec un objet B, il est nécessaire que l'objet A connaisse l'identité de B. On parle alors d'une dépendance entre les objets A et B. Cette dépendance, détectée lors de la phase d'analyse ou de conception d'un projet, donne naissance en phase de conception détaillée, à l'ajout d'un attribut de type B dans la classe A(1).
II-B. Comment initialiser les dépendances▲
Traditionnellement, deux stratégies peuvent être envisagées afin d'initialiser ces dépendances :
- utilisation de l'opérateur new ;
- mise en œuvre d'une factory (codée par le développeur, mise à disposition par un framework quelconque).
II-B-1. L'opérateur new▲
L'usage de l'opérateur new est souvent réservé à l'initialisation des dépendances liant des classes appartenant à la même couche architecturale (associations entre objets métiers).
public
class
Pet {
private
Bone os;
public
Pet (
){
this
.os =
new
Bone
(
);
}
}
II-B-2. La factory▲
La factory sera utilisée à des fins plus particulières : isoler l'instanciation de l'objet dépendant de l'initialisation de la dépendance elle-même (affectation de l'attribut avec la référence retournée par la factory). Les dépendances entre couches architecturales différentes sont particulièrement sujettes à une initialisation par l'intermédiaire d'une factory :
La couche service passe par une factory pour travailler avec les DAO(2).
public
class
PetShopImpl {
private
PetShopDao dao;
public
PetShopImpl
(
){
PetShopDao dao =
PetShopFactory.getInstance
(
);
}
}
L'avantage de l'utilisation d'une factory réside dans le fait qu'elle exploite parfaitement le concept d'implémentation d'interface (ou bien celui de l'héritage). On considère que l'utilisation d'une factory apporte des avantages en termes de découplage : en effet, cela permet de manipuler un objet au travers d'un type que l'on a souhaité générique sans se soucier de la classe réelle de l'objet manipulé. On note sur le diagramme « Figure 4. Une Factory - le découplage » que le programme qui doit manipuler la couche d'accès aux données n'a plus aucun lien avec la classe qui sera physiquement instanciée, il manipulera un objet au travers d'une variable de type ClientDao (qui est une interface).
Rappel : tout vient du fait que la factory utilisera un code ressemblant à :
public
PetShopDao getInstance
(
) {
PetShopDao result =
new
PetShopDaoImpl
(
); // new PetShopDaoBouchon()
return
result;
}
II-C. Et Spring dans tout cela ?▲
Spring, et plus précisément le mécanisme d'injection de dépendances offre un nouveau moyen d'initialiser les dépendances entre vos classes. Le principe est simple : là où vous implémentiez une factory qui contenait la règle d'instanciation de votre dépendance (cf. code ci-dessus), Spring vous apporte une factory générique totalement paramétrable à l'aide d'un fichier de paramétrage au format XML.
<beans>
<bean
id
=
"petdao"
class
=
"com.valtech.springtraining.dao.PetShopDaoImpl"
>
</bean>
<bean
id
=
"petshop"
class
=
"com.valtech.springtraining.dao.PetShopImpl"
>
<property
name
=
"dao"
>
<ref
bean
=
"petdao"
/>
</property>
</bean>
</beans>
Si l'on analyse ce fichier, on comprend assez aisément que l'on y déclare une dépendance entre un objet de type PetShopImpl et un PetDaoImp ; la dépendance est implémentée sous la forme d'une propriété dans la classe PetShopImpl portant le nom dao. Les deux objets identifiés par « petdao » et « petshop » seront instanciés par Spring qui se chargera de faire l'initialisation de notre dépendance.
Code à la charge de Spring :
petshop.setDao
(
petdao);
Comme nous pouvons le constater sur cet exemple, le principe même d'injection de dépendances est novateur dans le sens où il normalise (au sens Spring) un paramétrage extérieur d'une factory générique. Mais la puissance de Spring ne se limite bien évidemment pas à ce concept mais à l'utilisation que l'on peut en faire, les chapitres suivants détaillent cette utilisation avancée et structurante.
III. La notion de proxy-intercepteur▲
Ce chapitre évoque l'une des techniques permettant d'intercepter des invocations aux objets que vous avez implémentés. D'autres techniques existent et répondent globalement à la même problématique (AOP) mais ne sont pas décrites en détail ici.
III-A. Rappel : les proxies▲
Un proxy est un design pattern couramment utilisé dans le monde du développement.
Il désigne un mécanisme visant à masquer à un utilisateur nombre de traitements antérieurs et/ou postérieurs à celui qu'il pensait invoquer, et cela sans qu'il ait besoin d'apprendre une nouvelle API.
Le stub désigne l'artefact de programmation qui permet d'implémenter ce design pattern.
La mise en œuvre d'un proxy impose de respecter un certain nombre de bonnes habitudes au niveau de vos développements, à savoir principalement la généralisation de la programmation par interfaces (programmation par contrat).
III-B. Créer son proxy et l'utiliser avec Spring▲
Créer un proxy est relativement simple avec Spring, il suffit d'écrire une classe qui implémente l'interface org.aopalliance.intercept.MethodInterceptor.
public
class
DebugInterceptor implements
MethodInterceptor
{
private
final
Log logger =
LogFactory.getLog
(
getClass
(
));
public
Object invoke
(
MethodInvocation methodInvocation) throws
Throwable
{
logger.info
(
"Debut methode: "
+
methodInvocation.getMethod
(
).getDeclaringClass
(
) +
"::"
+
methodInvocation.getMethod
(
).getName
(
));
long
startTime =
System.currentTimeMillis
(
);
try
{
Object retVal =
methodInvocation.proceed
(
);
return
retVal;
}
finally
{
logger.info
(
"fin methode: "
+
methodInvocation.getMethod
(
).getDeclaringClass
(
) +
"::"
+
methodInvocation.getMethod
(
).getName
(
));
logger.info
(
"Temps d'exécution: "
+
(
System.currentTimeMillis
(
) -
startTime) +
" msecs."
);
}
}
}
Afin de pouvoir déclarer l'injection de cet intercepteur comme intermédiaire à votre traitement métier, il devient alors nécessaire de modifier le fichier de paramétrage de Spring :
<beans>
<bean
id
=
"petdaoimpl"
class
=
"com.valtech.springtraining.dao.PetShopDaoImpl"
>
</bean>
<bean
id
=
"petshop"
class
=
"com.valtech.springtraining.dao.PetShopImpl"
>
<property
name
=
"dao"
>
<ref
bean
=
"petdao"
/>
</property>
</bean>
<bean
id
=
"debuginterceptor"
class
=
"com.valtech.interceptor.DebugInterceptor"
/>
<bean
id
=
"petdao"
class
=
"org.springframework.aop.framework.ProxyFactoryBean"
>
<property
name
=
"proxyInterfaces"
>
<value>
com.valtech.springtraining.dao.PetShopDao</value>
</property>
<property
name
=
"interceptorNames"
>
<list>
<value>
debugInterceptor</value>
<value>
petDaoImpl</value>
</list>
</property>
</bean>
</beans>
La factory générique de Spring joue ici son rôle et masque à l'utilisateur l'initialisation de l'objet petshop grâce au fait que sa dépendance ait été initialisée sous forme d'un proxy. Ce dernier respectant l'interface PetShopDao, le développeur ne se rend pas compte qu'il utilise en fait un intermédiaire qui va utiliser le debugInterceptor avant de déléguer à l'objet cible qu'est petDaoImpl.
Aussi, dans la classe PetShopImpl, l'utilisation du dao telle que mentionnée ci-dessous :
public
class
PetShopImpl {
PetShopDao dao;
&
#8230
;
// Implementation des accesseurs vers dao
public
void
ajouter
(
Pet p) {
dao.sauve
(
p) ;
}
}
aura le résultat suivant :
Debut methode: interface com.valtech.springtraining.dao.PetShopDao::sauve
// invocation de la méthode sauve de PetShopDaoImpl
Fin method: interface com.valtech.springtraining.dao.PetShopDao::sauve
Temps d'exécution : 10 msecs.
III-C. Spring et les intercepteurs▲
C'est la plus-value majeure de Spring ! Mais à quoi cela peut-il bien servir ? Comme évoqué ci-dessus, le proxy désigne un mécanisme permettant d'exécuter des prétraitements et des post-traitements aux méthodes développées dans vos classes par vos soins. Un grand nombre de services techniques peuvent être implémentés par ces mécanismes, allégeant ainsi le code à la charge du développeur. Attention néanmoins à ne pas tomber dans le piège d'utiliser ces mécanismes afin d'implémenter la logique métier de vos applications, il sera préférable de focaliser l'utilisation de ces mécanismes d'intercepteur à des fins techniques transverses :
- démarcation des transactions ;
- création et/ou vérification de contexte de sécurité ;
- création et/ou utilisation de contexte Hibernate (session).
Bien évidemment, plusieurs de ces intercepteurs transverses existent déjà :
- org.springframework.transaction.interceptor.TransactionProxyFactoryBean pour la démarcation transactionnelle (capable de s'interfacer avec un manager de transaction piloté par Hibernate ou un autre outil de mapping objet relationnel) ;
- le framework ACEGI pour la sécurité ;
- …
La liste des intercepteurs n'est bien évidemment pas exhaustive : n'importe qui est en mesure d'implémenter son propre intercepteur et de le mettre à la disposition de la communauté. Je vous invite néanmoins à clairement identifier ceux qui vous seront utiles sur vos projets. Les plus structurants restant néanmoins le TransactionProxyFactoryBean pour démarquer vos transactions ainsi que ACEGI pour gérer la sécurité et les habilitations.
IV. Une puissante bibliothèque de classes utilitaires▲
Spring comme on l'a vu, autorise une gestion de vos dépendances simplifiée et externalisée. L'utilisation conjointe de l'injection de dépendances et des intercepteurs permet de faire bénéficier vos objets de services techniques puissants et robustes.
Spring met en même temps à votre disposition un ensemble de classes utilitaires véritablement intéressantes, puissantes et simples d'utilisation. Nous allons nous attarder plus précisément aux classes JdbcTemplate et hibernateDaoSupport.
IV-A. JdbcTemplate▲
À se demander pourquoi cette classe (ou ses dérivés : NamedJdbcTemplate) ne sont pas encore intégrées au JDK tellement leur utilisation relève du bon sens :
- lisibilité ;
- robustesse ;
- concision.
Exemple d'utilisation d'un JDBCtemplate qui extrait une liste contenant les noms de tous les utilisateurs.
JdbcTemplate template =
new
JdbcTemplate
(
dataSource);
List names =
template.query
(
"SELECT USER.NAME FROM USER"
,
new
RowMapper
(
) {
public
Object mapRow
(
ResulSet rs, int
rowNum) throws
SQLException {
return
rs.getString
(
1
);
}
}
);
Un NamedQuery qui insère des données dans une table !
String
sql
=
"INSERT INTO TBL_USER(NAME, FIRSTNAME) "
+
"VALUES(:name, :firstname)"
;
Map<
String
, String
>
namedParameters =
new
HashMap<
String
, String
>()
;
namedParameters.put(
"name"
, "Picasso"
)
;
namedParameters.put(
"firstname"
, "Pablo"
)
;
NamedParameterJdbcTemplate template =
new
NamedParameterJdbcTemplate(
datasource)
;
template.update
(
sql
, namedParameters)
;
On remarque tout le travail effectué par les classes utilitaires de Spring :
- gestion de la connexion via une datasource (avec intégration dans une éventuelle transaction courante) ;
- gestion des exceptions ;
- parfaite libération des ressources (Connection, statement, resultset…).
IV-B. HibernateTemplate ou HibernateDaoSupport▲
La manipulation d'un HibernateTemplate permet à un développeur Hibernate aguerri d'être très performant et à un développeur Hibernate débutant d'aller droit dans le mur. Je déconseille vivement à un développeur débutant dans le monde Hibernate qui ne serait pas encadré d'utiliser les fonctionnalités proposées par Spring pour travailler avec Hibernate (ou tout autre outil de mapping objet relationnel). Néanmoins, une utilisation maîtrisée de Spring pour faire de l'Hibernate colle parfaitement à une approche structurante permettant une utilisation industrielle d'Hibernate.
L'effet pervers de l'utilisation de cette classe est de vous masquer un certain nombre d'opérations structurantes pour Hibernate :
- durée de vie de la session Hibernate ;
- gestion des transactions ;
- flush des opérations de persistance.
Ces stratégies demeurent bien évidemment paramétrables au travers de Spring, mais une mauvaise utilisation peut avoir un impact désastreux sur les performances de l'application.
Comme pour les JdbcTemplate, Spring se charge de gérer les exceptions, de libérer les ressources, et éventuellement de fermer la session Hibernate (c'est bien là le problème !!) et offre ainsi un modèle de programmation épuré :
public
class
ProductDaoImpl extends
HibernateSupport implements
ProductDao {
public
Collection loadProductsByCategory
(
String category) throws
DataAccessException {
return
getHibernateTemplate
(
).find
(
"from test.Product product where product.category = ?"
, category);
}
}
V. Conclusion▲
Nous avons vu dans ce document la simplicité d'utilisation que procure Spring dans le cadre des développements. Parfaitement adapté à des processus de développement moderne notamment en ce qui concerne la testabilité de nos applications en dehors d'une infrastructure technique lourde (par opposition avec les EJB dans un serveur d'application).
Il est néanmoins important de cadrer l'utilisation de Spring afin d'éviter les dérives correspondant à des abus d'injection de dépendances. Autant il me semble pertinent de lier l‘ensemble des couches architecturales : présentation, service et accès aux données, autant il me semble superflu d'utiliser l'injection de dépendances dans une de ces trois couches.
L'utilisation de Spring sur l'ensemble des couches architecturales d'une application permet en effet de le qualifier de framework à tout faire. Attention néanmoins aux abus d'usage, et surtout de considérer Spring comme la solution qui masque la complexité des framework auxquels il se couple : une maîtrise préalable du framework sous-jacent est un impératif.
VI. Remerciements▲
Cet article a été mis au gabarit de developpez.com : voici le lien vers le PDF d'origine : Spring-FrameworkAToutFaire.pdf.
L'équipe Java de Developpez.com tient à remercier la société Valtech pour la mise à disposition de cet article aux membres de Developpez.com.
Nous tenons à remercier également f-leb pour sa relecture attentive de cet article et Régis Pouiller pour la mise au gabarit.