Spring et les design patterns
Introduction
Dans l’écosystème Java actuel, il est devenu de moins en moins courant de créer de nouvelles fonctionnalités techniques à partir de zéro. Au lieu de cela, le développement s’oriente vers l’assemblage de briques logicielles préexistantes issues de frameworks et de librairies open source. L’accent est mis sur la création de composants fonctionnels réutilisables pour maximiser la mutualisation des développements. Il est crucial de ne pas réinventer ce qui existe déjà et de s’efforcer de rendre son code réutilisable.
La maintenabilité du code est une préoccupation majeure, car souvent une équipe code tandis qu’une autre s’occupe de la maintenance. Sur des logiciels d’envergure développés sur plusieurs années, il est essentiel de voir son code comme un élément d’un framework plus large, destiné à être réutilisé. Ce code doit être modulaire, bien structuré, correctement documenté (par exemple avec la javadoc et des snippets en Java 18+), testable et testé.
Nous générons une partie de la documentation du code et des API via des métadonnées, en utilisant des outils comme SpringFox ou les annotations OpenAPI.
La compréhension et l’utilisation...
Singleton en Java
Un singleton désigne un objet qui est unique dans un contexte donné. Cet objet est instancié uniquement lors de sa première sollicitation et cette instance est ensuite réutilisée à chaque besoin. Ce pattern est particulièrement utile pour partager une ressource commune ou pour garantir qu’une séquence de traitement spécifique est représentée par une seule instance. Ainsi, une même méthode peut être employée par plusieurs processus, que ce soit de manière séquentielle dans un environnement à thread unique ou de manière concurrente dans un environnement multi-threadé grâce à ses propriétés de réentrance.
1. Préoccupation
À l’origine, dans la programmation orientée objet, les actions spécifiques étaient souvent implémentées directement dans les objets qui en avaient besoin. Cela entraînait une redondance, avec de multiples copies des mêmes actions réparties à travers diverses instances d’objets. Cette approche découlait de la nature même de la programmation orientée objet, où les traitements sont typiquement encapsulés avec les données au sein des objets.
Pour optimiser cette situation, en particulier pour les actions ne dépendant pas de l’état de l’objet, une solution a été de déplacer ces actions dans la partie statique de la classe. Cette portion d’une classe étant commune à toutes ses instances, cela éliminait la redondance du code en mémoire. Ainsi, certains services, ne dépendant pas des données qu’ils traitent, ont été regroupés dans des classes principalement statiques. Cette organisation a conduit à une architecture séparant les données (sous forme de POJO, ou Plain Old Java Objects, qui sont des objets sans logique métier autre que les constructeurs et accesseurs) des traitements, un peu à la manière de la programmation en langage C. Toutefois, cette architecture a vite montré ses limites.
C’est là qu’est apparu le concept de singleton. Un singleton est une classe instanciée une seule fois, dont l’instance unique est ensuite...
Inversion de contrôle
Sous le principe de l’Inversion of Control (IoC), au lieu d’instancier et d’appeler directement les classes qui implémentent les fonctions d’une bibliothèque, le programme spécifie simplement son besoin d’une bibliothèque et celle-ci lui est automatiquement fournie. Dans ce cadre, c’est un mécanisme externe, tel qu’un framework ou un système de plugins, qui prend en charge la mise à disposition de la classe. Par exemple, dans Spring, l’application déclare avoir besoin d’un service avec une API spécifique, et Spring lui attribue le candidat le plus approprié.
Spring permet de désigner le service souhaité, généralement via une interface ou, en l’absence d’interface, via une classe spécifique. Ce processus est souvent réalisé à l’aide d’une fabrique d’objets, elle-même un design pattern, qui sert à fournir l’objet demandé.
List<String> liste = new ArrayList<String>();
LivreDao dao = (LivreDao)factory.getBean("livreDao");
Il est recommandé en Java de déclarer un objet en utilisant l’interface qui le caractérise le mieux, plutôt que d’instancier soi-même l’objet. On confie à Spring la tâche de trouver la classe la plus appropriée...
Façade
Un pattern de façade est utilisé pour regrouper les API de plusieurs classes en un seul point d’accès, simplifiant ainsi l’interface pour les utilisateurs de ces API. Spring JDBC est un exemple typique de ce pattern. Il agit comme une façade qui cache les différences entre les implémentations spécifiques des diverses bases de données sous une API commune. En plus de masquer ces différences, Spring JDBC simplifie la gestion des exceptions liées aux bases de données. Il fournit une interface unifiée qui encapsule les opérations diverses spécifiques à chaque base de données, les rendant ainsi apparemment identiques et plus simples à gérer pour les utilisateurs de la façade.
Un service Spring, marqué par l’annotation @Service, peut être vu comme une façade qui, d’une part, expose des API métier et, d’autre part, interagit avec les DAO (Data Access Objects) pour réaliser les opérations métier et les opérations liées aux bases de données. Cette façade offre ainsi une API à la fois simple et facilement testable. Les programmes qui utilisent cette façade n’ont plus besoin de gérer directement les dépendances, car celles-ci sont intégrées et gérées au sein de la façade...
Fabrique
Spring emploie un modèle de fabrique (factory pattern) pour générer des beans, en s’appuyant sur une référence au contexte applicatif. La configuration de ces beans est spécifiée dans la configuration. Une portion de cette configuration est établie de manière fixe, tandis qu’une autre est dérivée selon des règles assez complexes. À travers ce processus, Spring identifie et sélectionne la meilleure implémentation disponible pour un contrat d’API spécifique.
BeanFactory factory = new XmlBeanFactory(new
FileSystemResource("spring.xml"));
Triangle triangle = (Triangle) factory.getBean("triangle");
triangle.draw();
Spring offre une gamme étendue de fabriques pour répondre à une variété de cas d’utilisation, tout cela via l’interface BeanFactory.
Dans une application autonome (standalone), la fabrique (factory) de Spring est souvent explicitement visible. Dans ce cas, on utilise généralement une fabrique qui s’appuie sur la configuration. Par contre, dans le contexte d’une application web, la fabrique est généralement dissimulée dans un filtre de servlet.
Décorateur
Le pattern décorateur consiste à utiliser un composant concret qui met en œuvre une interface donnée. Le décorateur, quant à lui, est une extension de cette même interface. Il encapsule l’objet d’origine, y ajoutant des attributs ou des méthodes supplémentaires. Ce modèle permet d’intercepter et de modifier le comportement des méthodes, et repose sur le principe de l’héritage pour enrichir ou modifier les fonctionnalités de l’objet initial.
Spring utilise le design pattern Décorateur dans plusieurs endroits, mais un exemple notable est dans le package java.io.
Le pattern Décorateur est utilisé pour ajouter dynamiquement de nouvelles fonctionnalités à un objet sans modifier sa structure de classe. C’est une alternative à la création de sous-classes pour ajouter de nouvelles fonctionnalités.
Dans Spring, par exemple, vous pouvez avoir un InputStream de base, et vous pouvez le décorer avec un BufferedInputStream pour ajouter des fonctionnalités de mise en tampon. Vous pouvez ensuite décorer davantage cet objet avec un DataInputStream pour ajouter des méthodes pour lire les types de données Java primitifs. Chaque décorateur ajoute une nouvelle fonctionnalité tout en conservant l’interface de l’objet original.
Un exemple...
Proxy
Spring utilise abondamment le design pattern proxy, en particulier pour les aspects liés à la programmation orientée aspect (AOP) et aux accès distants. L’application de ce pattern dans le contexte de l’AOP est expliquée en détail dans le chapitre Programmation orientée aspect avec Spring.
Spring ne donne pas un accès direct aux objets, mais via un objet intermédiaire appelé proxy, ce qui permet à Spring de modifier le comportement de l’objet d’origine.
Par exemple :
interface Pojo3 {
public void foo();
}
@Slf4j
class SimplePojo3 implements Pojo3 {
public void foo() {
log.info("foo");
}
}
@Slf4j
class RetryAdvice implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target)
throws Throwable {
log.info("Après "+method.getName()); ...
Modèle Vue Contrôleur (MVC)
Spring MVC permet de faire simplement des applications web en séparant les trois éléments principaux :
Le modèle |
Les données |
La vue |
Ce qui est affiché |
Le contrôleur |
Le traitement sur les données et l’enchaînement des vues |
Spring délègue le choix de la vue à un ViewResolver qui est, lui-même, un design pattern.
Par exemple, le contrôleur suivant sur la sollicitation de l’URL /bonjour demandera l’affichage de la page bonjour.jsp en lui fournissant la date pour que la page l’affiche :
@Controller
public class BonjourController {
@RequestMapping("/bonjour")
public ModelAndView bonjour() {
ModelAndView mav = new ModelAndView();
mav.setViewName("bonjour");
mav.addObject("date", new Date());
return mav;
}
Templates
On utilise des templates (modèles) pour tous les codes génériques.
Spring utilise massivement des templates. Voici quelques exemples :
Template |
Module |
Utilité |
RepeatTemplate |
Spring Batch |
Implémentation de RepeatOperations |
JdbcTemplate |
Spring |
Opération courante JDBC |
QueryDslJdbcTemplate |
Spring Data |
Support QueryDsl |
NamedParameterJdbcTemplate |
Spring |
JdbcTemplate, mais avec des paramètres nommés |
TransactionTemplate |
Spring |
Gestion générique des transactions |
HibernateTemplate |
Spring |
Opérations courantes sur Hibernate |
JdoTemplate |
Spring |
JDO générique |
JpaTemplate |
Spring |
Opération JPA |
JpaTransactionManager |
Spring |
Transactions JPA |
RestTemplate |
Spring |
Web service REST |
SimpMessagingTemplate |
Spring |
Message (par exemple JMS) |
Ce livre présente des exemples pour l’utilisation de certains de ces templates.
Les templates sont une grande force pour Spring car ils simplifient le code. Ils masquent la complexité et il est même possible d’interfacer un système exotique en étendant un template Spring et d’offrir une interface standard aux développeurs.
Stratégie
L’injection de dépendances (DI) est un exemple du pattern de stratégie. À chaque fois que vous souhaitez permettre l’interchangeabilité entre différents beans, une interface est utilisée. Cette interface est ensuite reliée à un constructeur ou à une méthode setter appropriée dans la classe cible. Cette approche permet d’intégrer facilement différentes implémentations de l’interface dans la classe.
Avec Spring, pour une interface donnée, il est possible de demander l’injection d’un objet qui implémente cette interface, offrant ainsi la possibilité de choisir parmi plusieurs objets ayant des API similaires, mais des comportements distincts. Prenons l’exemple de l’interface List : selon les besoins spécifiques en termes de comportement, différentes implémentations comme AbstractList, AbstractSequentialList, ArrayList, AttributeList, CopyOnWriteArrayList, LinkedList, RoleList, RoleUnresolvedList, Stack, Vector, et d’autres peuvent être choisies. Cela permet une grande flexibilité dans la sélection de la fonctionnalité souhaitée.
Points clés
Points à retenir :
-
Le framework Spring est massivement orienté design pattern par nature.
-
Il faut identifier les design patterns pour les signaler dans les commentaires du code.
-
Avant de coder quelque chose, il faut voir s’il s’agit d’un design pattern et réutiliser ou spécialiser du code qui implémente déjà ce patron de conception.
-
Coder avec l’esprit design pattern permet à son code d’être plus facilement réutilisable.
-
Nous utilisons Spring pour créer des singletons.