AOP Concepts et implémentations

Une des préoccupations principales du génie logiciel est de concevoir des systèmes modulaires et flexibles. Dans cette démarche, le point central de toute réflexion est de réduire au minimum, voir de supprimer, les dépendances et les couplages entre modules.

Le génie logiciel a beaucoup misé sur la programmation orientée objet POO, qui malgré ses limites, a énormément apporté en terme de structuration de logiciels, en terme de souplesse d’évolution et surtout en terme de productivité.

Durant les deux dernières décennies, ce sont ces bénéfices qui ont permit l’adoption de la POO par le marché. Mais face à l’exigence croissante des systèmes en terme de flexibilité et de modularité, l’impact de ces limites est devenu lourd et problématique.

Ceci a conduit à l’émergence de nouveaux paradigmes qualifiés de « post-objet », dont la programmation orientée aspect POA (AOP). Les concepts de ce paradigme sont des extensions de la POO et répondent en particulier à ce besoin de flexibilité et de modularité.

A travers une série d’articles sur ce sujet nous souhaitons participer à l’adoption de ce paradigme.

I.    Limites de la POO

Largement utilisées par le génie logiciel, les techniques de la POO n’ont pas permis une séparation claire des responsabilités (métier et technique) endossées par les classes/composants du système en vue de leur réutilisation, Quant au découpage en couches et l’utilisation de cadre de développement (Framework), ils ont permis d’amener de la structuration et de la clarté dans les architectures, mais n’ont pas permis de pallier à cet entrecroisement des responsabilités.

L’exemple ci-dessous montre comment la gestion des traces est appelée dans les différentes couches d’une application, ceci par les différents services comme l’authentification, la persistance et la synchronisation, eux mêmes souvent appelés de la même manière.

Figure 2.1 : Entrecroisement et dépendance

Figure 1.1 : Entrecroisement et dépendance.

Ce schéma nous rappelle en particulier l’impact de deux promesses qui n’ont pas été pleinement tenues. Il s’agit de « modularité » et de « réutilisation ».

Indépendance des classes

Le découpage en classes assuré par la POO ne résout pas ce problème, car même dans les meilleures architectures, les classes englobent les appels à plusieurs  fonctionnalités en même temps (ex : code métier + sécurité + traces).

La dispersion du code

La POO a permis de centraliser/localiser l’implémentation d’un service dans une classe spécialisée, mais les appels à ce service peuvent venir des différentes couches d’un système (logiciel ou application). Dès lors, ce qui était un point fort devient une grande faiblesse que la POO ne sait pas résoudre. En effet, si la modification de l’implémentation reste localisée et n’impacte pas les classes utilisatrices du service, toute modification de la signature de ce dernier impliquera une modification de toutes les classes clientes. Ceci pose problème en terme de facilité de maintenance et d’évolution (flexibilité et modularité réduite) (commentaire : la dispersion  du code est proportionnelle à son utilisation).

Nous retiendrons, que via les concepts de classes et d’interfaces (mécanismes d’héritage et de polymorphisme), la POO fournit une bonne séparation des fonctionnalités verticales (métier), mais elle ne réussit pas aussi bien pour les fonctionnalités transversales (techniques).

II.    LA PROGRAMMATION ORIENTEE ASPECT

La POA est un nouveau paradigme qui puise ses origines dans la métaprogrammation, la réflexivité et les protocoles à méta-objet.

Elle est née en 1996 dans les laboratoires de Xerox PARC – Californie grâce aux travaux de Gregor KICZALES et de son équipe. Ces travaux se sont concrétisés très vite à travers la création de nouveaux langages: AspectJ (1998 – POO JAVA), AML (optimisation du calcul matriciel) RIDL (interfaces de communication distante), etc.

La POA a été adoptée assez tôt par les experts (MDA et programmation générative, Spring, Jboss, Hibernate) et elle est actuellement en voie de l’être par la communauté des développeurs.

Apports de la POA

La POO a bien réussi la séparation verticale des responsabilités (classes métier et technique spécialisées), mais elle n’a pas pu apporter des solutions de modularité garantissant une réutilisation sans dépendance de ces fonctionnalités.

Née de ce constat, la programmation orientée aspect propose de rendre modulaires les fonctionnalités transversales dont le code finit par être dispersé dans les différentes couches d’une application (Gestion des traces, authentification, sécurité, transactions, persistance …) et décrit les mécanismes permettant  de les externaliser en modules réutilisables appelés « Aspects ».

Le but de la POA est de compléter les langages existants (objets et procéduraux) et non de les remplacer. Il s’agit donc, d’une technologie transversale qui n’est pas liée à un langage particulier. Elle dépend seulement du tisseur d’aspect, qui lui, est lié à un langage.

Différentes implémentations existent (AspectJ, AspectC, AspectC#, AspectC++ …). Elles permettent aux différents langages de mieux gérer le concept de séparation des responsabilités.

Figure3.1 : Séparation des responsabilités.

Figure3.1 : Séparation des responsabilités.

Le schéma ci-dessus montre la différence entre une application POO standard et la même utilisant l’AOP. Il montre clairement les bénéfices qu’apporte une telle technologie :

  • Capture des complexités en entités modulaires et réutilisables (finie la dispersion du code)
  • Parallélisassion des développements et gain de productivité
  • Réduction des coûts de la maintenance et des évolutions

Principes & concepts

L’AOP exploite le principe de séparation des responsabilités. Autrement dit, chaque entité doit s’occuper d’une seule préoccupation. Un système utilisant la POA se trouve ainsi, décomposé en deux types d’entités spécialisées :

  • Classes: entités s’occupant des traitements métiers,
  • Aspects: entités transversales réutilisables (traces, authentification, sécurité, transaction …).

Les deux types de codes doivent rester séparés pour ne pas reproduire les problématiques de dépendances et d’entrecroisement (Crosscutting).

La modularité et la réutilisabilité sont apportées par un niveau d’abstraction supplémentaire assuré par un mécanisme de tissage (statique ou dynamique). Les liens entre les deux types de code sont tissés (réalisés) au moyen d’un tisseur d’aspect, qui se charge d’injecter du code technique dans le code métier, en respectant des règles prédéfinies indiquant les niveaux d’injections. On parle de greffon et de point de jonction.

Enfin, pour garantir cette séparation des deux types de codes, l’opération de tissage ou l’injection doit être réalisée soit à la compilation (stratégie statique) soit à l’exécution (stratégie dynamique).

Considérant une spécification de type :

«Avant toutes opérations sur le système, le client doit s’authentifier et ses droits identifiés»

Si on décompose selon la POA, on distinguera une:

  • Composante métier : Opération et client (fonctions et entités du système),
  • Composante technique : Authentification et gestion de profil (composantes transversales qui peuvent être sollicitées à différents niveaux du système),
  • Règle à respecter. Du point de vue POA, cette règle détermine l’endroit où le tissage sera effectué et peut être spécifié de façon déclarative via un de point de jonction.

Les appels directs entre modules (représentés par la figure 2.1) disparaissent au profit d’une approche déclarative basée sur des expressions relationnelles portées par les aspects eux mêmes.

Lexique

Comme tout nouveau paradigme, la POA vient avec un certain nombre de mots clefs qui définissent et concrétisent ses concepts.

Aspect : module représentant une fonctionnalité transversale utilisée par une application ou un système. Il correspond à un ensemble d’instructions (greffon/advice ou programme) et leurs points d’activation dits points de jonction (pointcut).

De la même manière qu’une classe java peut hériter, définir des méthodes et des attributs, un aspect peut, également, hériter ou définir des attributs et des méthodes.

Pointcut : appelé également coupure, il déclare et spécifie l’instruction ou les instructions métier à intercepter. La fusion entre « métier » et « aspect » est réalisée par le tisseur. Elle se fait au niveau des points de fusion ou points de jonction (JoinPoint).

Les expressions régulières, les opérateurs unaires (!) et binaires (&& et ||) peuvent être utilisés dans la définition d’une coupe.

JoinPoint : indique les points précis d’exécution où le tisseur d’aspect injectera du code (greffons). Il permet de récupérer le contexte de l’appel, donc de l’objet sur lequel se fait l’appel de la méthode ainsi que les paramètres de la méthode (introspection).

Greffon : Programme ou instruction(s) exécutée(s) à des niveaux précis du code métier.

Weaver : Outil permettant de réaliser la fusion entre le code métier et une préoccupation transversale externalisée sous forme d’aspect. L’opération de fusion dite « tissage (weaving) » peut être effectuée de manières statique (à la compilation) ou dynamique (à l’exécution).

Différents tisseurs open source existent. Ils implémentent une de ces deux stratégies. Les plus connus sont AspectJ, Spring AOP et Jboss AOP, Aspect Werkz.

Dans ce document, nous nous intéresserons en particulier à AspectJ (tissage statique), à Spring AOP et Jboss AOP (tissage dynamique).


III.    IMPLEMENTATION ASPECTJ

A la fois un langage et un outil (plugin, tisseur), il est considéré comme le plus complet permettant d’implémenter l’AOP. Il est aussi le plus utilisé des langages AOP.

AspectJ est une extension du langage JAVA. Il apporte une syntaxe de composition à la fois statique et dynamique permettant de fusionner les aspects fonctionnels avec les aspects techniques.

AspectJ permet donc, deux types de composition :

  • Statique : elle permet de modifier le système (classes et interfaces) par ajout d’attributs, de méthodes, ou par ajout d’une gestion des avertissements (warnings) et des erreurs.
  • Dynamique : permet d’ajouter de nouveaux comportements lors de l’exécution, d’étendre ou de remplacer certains traitements.

NB : Nous nous sommes basés sur AspectJ pour illustrer l’exemple de notre article (Chapitre suivant).


IV.    AOP PROGRAMMATION AVANCEE

Pour illustrer à la fois la puissance et la facilité d’utilisation des principes de la POA, nous avons choisi, à travers un exemple de gestion des droits, d’aborder l’implémentation d’un des design patterns. Il s’agit du patron « Proxy ». L’objectif est d’apprécier le raffinement que peut apporter la pratique de la programmation par aspect.

Rappel

Les design patterns ou patrons de développement sont des motifs standards complètement indépendants des langages de programmation. Ils sont de différents types (création, structure, comportement) et ne peuvent être utilisés que pour répondre à certaines problématiques particulières.

Les patrons sont de bons candidats à l’implémentation AOP car une partie du code de ces derniers est propre à leur mise en place et donc loin des préoccupations métier (rigidité du code patron, localisation/ non réutilisation du code …). L’externalisation en aspect est, donc, une bonne approche pour les contrôler et les rendre transversaux.

Patron Proxy

  • Ce patron de développement permet à travers un objet de substitution d’accéder à un objet dit original. Très générique, il se décline en pratique en deux types principaux:
  • Proxy d’accès (protection/perf) : vérifie les accès à un objet (instanciation coûteuse, chargement coûteux en ressource, gestion de droit/profile, …) avant d’en fournir un substitut.
  • Proxy distant ou distribué : accès à un objet distant via un représentant local (espaces d’adressage différents) en cachant toute la complexité de la communication réseau.

Dans l’exemple qui suit, nous considérons un proxy de gestion de droit d’accès :

Diagramme Proxy Uml.

Diagramme Proxy Uml.

Cet exemple met en évidence quelques inconvénients du patron proxy. Nous noterons que :

  • Le développement de la classe intermédiaire (proxy) peut rapidement devenir lourd car elle doit être synchronisée avec chaque modification de la classe originale.
  • Dans notre classe de test (classe client), rien ne nous empêche de faire appel directement à l’objet original (instanciation directe par un collaborateur ne connaissant pas l’existence du proxy). Dès lors, si nous souhaitons imposer le passage par le proxy ceci met en évidence l’absence de mécanisme intrinsèque de contrôle.

La POA apporte une solution concrète à ces deux inconvénients qui de notre point de vue sont majeurs. L’exemple ci-dessous montre la puissance que confère la POA au design pattern.

Commençons par décrire un aspect abstrait que nous pouvons spécialiser selon les besoins. L’objectif est d’avoir un aspect générique et réutilisable. Ce dernier définit les méthodes à implanter, ainsi que le greffon réutilisable (utilisation AspectJ).

package fr.soat.aop.aspects.dp;

import fr.soat.java.busines.User;

/** Aspect de type abstrait destiner à l'héritage */
public abstract aspect AbstractProxyAspect {

protected User user;

/** Méthode abstraite : à redéfinir pour tester profile */
protected abstract boolean isAdmin();

/** Point de coupure abstrait à redéfinir par héritage */
protected abstract pointcut controlerAcces();

/** Code greffon mis en commun */
before() : controlerAcces() {
if(!isAdmin()) {// lancement d'une exception fonctionnelle
throw new RuntimeException("Accès Refusé : droits insuffisants"
+ thisJoinPoint.getSignature().getName());
}
}
}

Les aspects dérivés doivent redéfinir les méthodes abstraites déclarées.

package fr.soat.aop.aspects;

import fr.soat.aop.aspects.dp.AbstractProxyAspect;

public aspect AdminAccesProxyAspect extends AbstractProxyAspect {

/** Implémentation basique utilisée par la gestion de profile */
protected boolean isAdmin() {
return user.isAdmin();
}

/** Gestion de tous les appels aux méthodes de type mutateur */
protected pointcut controlerAcces():call (* ProfileManager.set*(..));

}

L’utilisation d’un module aspect combiné à un patron de développement permet :

  • D’externaliser le code propre à l’utilisation du patron (fonctionnalité transversale). Dans notre cas, il s’agit de l’implémentation du patron proxy.
  • L’utilisation du patron de façon non intrusive. Dans notre exemple, tout appel aux méthodes de la classe « ProfileManager », passera forcement par le proxy. L’utilisateur ne pourra plus instancier directement la classe concernée par la gestion d’accès. Une meilleure maîtrise du design de l’application devient possible.

Conclusion

Dans l’approche AOP, l’analyse joue un rôle très important. Il est recommandé de bien identifier les aspects transversaux pour pouvoir les modulariser et les réutiliser. Sauf cas simple, l’absence d’une bonne analyse ne conduira qu’à l’externalisation d’une partie du traitement. Au final, la dispersion du code persistera et nous n’aurions gagné ni en clarté ni en modularité.

HANNEMANN et KIRCZALES ont défini quatre critères d’évaluation d’une bonne mise en place des concepts AOP :

  • Localisation : le code technique doit être centralisé dans un aspect
  • Réutilisation : la définition d’un aspect doit être assez générique pour pouvoir être utilisé par plusieurs classes.
  • Composition : une classe bénéficiant de l’utilisation d’un aspect, peut participer dans d’autres patrons car les aspects ne sont pas intrusifs. En cas de composition, il faudra être vigilant pour éviter d’éventuels effets de bord.
  • Adaptation : le lien entre la classe et l’implémentation de l’aspect est faible. L’évolution de ce dernier peut s’effectuer sans conséquences sur la classe métier.

Si l’un ou plusieurs (hormis la composition) de ces critères ne sont pas remplis, la mise en place de la POA est non recommandée.

Pour finir, il faut savoir que le raffinement par POA ne s’applique pas à tous les patrons de développement. L’étude de HANNEMANN ET KIRCZALES conclut que les patrons usine, usine abstraite, façade et intercepteur (pour ne citer que les plus utilisés) ne tirent aucun profit de la POA.

V.    Conclusion

Il s’agit donc de concepts et ensembles de techniques dont le but, quand ils sont bien utilisés, est de permettre:

–          Un Gain de productivité : en identifiant et séparant les logiques métiers, des différents aspects applicatifs et techniques. Les tâches de développement peuvent être quantifiées et parallélisées. Couplés à une méthodologie de travail comme XP qui permet un mixage des compétences, les membres d’une équipe pourront intervenir aussi bien sur les fonctionnalités métier que sur les préoccupations transversales.

–          Une Réutilisabilité & flexibilité : Les modules n’interagissent avec le système qu’à travers des aspects. Ils peuvent être spécialisés ou changés sans risque pour le système. Les nouvelles fonctionnalités peuvent être elles-mêmes implantées sous forme de nouveaux modules.

Une Facilité de Maintenance : la séparation des préoccupations (Séparation of concernes) conduit à la spécialisation du code. La lisibilité (qualité) s’en trouve améliorée.

Nombre de vue : 395

COMMENTAIRES 7 commentaires

  1. J’ai apprécié la lecture de cet article très bien écrit de surcroît, félicitation à l’auteur !
    Je pense que nommer la méthode abstraite “isAdmin” en “hasAccess” montrerait mieux l’intérêt de la classe AbstractProxyAspect.

  2. abdelmajid.lali dit :

    Bonjour,
    Tu as absolument raison. Dans un contexte plus général, j’aurai préféré «hasAccess» à «isAdmin». En effet, le diagramme UML est tiré d’un exemple de code ou j’utilise deux méthodes (ajouterDroit() et supprimerDroit()) qui appartiennent plus au domaine de l’administration, donc plus qu’une simple gestion d’accès.
    Ceci dit ta remarque est judicieuse, elle nous rappelle l’importance du nommage de nos méthodes.

  3. Cédric Corsellis dit :

    Hello,
    j’ai lu avec intérêt ton article. En résumé, la plus-value principale de la PAO semble être d’utiliser dans les couches basses (techniques) des contextes d’appel différents en utilisant des aspects spécialisés différents. Me trompai-je ? J’ai une question par rapport aux tisseurs statiques, lorsqu’un aspect est programmé en JAspect, le compilateur/tisseur produit donc du code exclusivement java au final ?

    Petites rqs :
    – positionner le lexique en intro
    – il n’y a pas de lien vers le blog depuis l’intranet

    Cédric.

  4. Cédric Corsellis dit :

    (pardon AOP …)

  5. abdelmajid.lali dit :

    Bonjour,
    1/ L’apport principal est la séparation des responsabilités dites verticales (logique métier) des autres responsabilités dites horizontales (traitement technique et applicatif) : modularité et flexibilité.
    2/Le tisseur, lors de la compilation donc lors de la production du bytecode, va injecter du code défini par les aspects dans le code métier. Au final on aura du bytecode et non du java.
    NB: Seules les premières générations de tisseurs généraient du code java après injection (méthode abandonnée).

  6. Daniel L dit :

    C’est vraiment agréable de lire un article aussi clair et concis. Belle performance 🙂

  7. […] On peut trouver beaucoup de tutoriaux sur spring AOP avec l’exemple de logger. Mais, quelque part, cet exemple est un peu déroutant pour les nouveaux arrivants. Alors, je vais vous présenter ici un exemple qui explique comment utiliser Spring AOP pour le profilage de temps d’exécution d’une fonction. L’exemple servira aussi pour connaitre simplement le temps d’exécution de certaines de vos fonctions si vous n’avez pas accès à JProfiler ou autres outils de profilage. Si vous voulez connaitre les principes fondamentaux de l’AOP je vous invite à lire l’article : “AOP Concepts et implémentations” […]

AJOUTER UN COMMENTAIRE