Un bref historique d'Avalon et un aperçu de tous les principes de conception qui l'ont créé
Les choses ont commencé avec le projet Apache JServ. Stefano Mazzocchi et d'autres personnes qui ont contribué au développement d'Apache JServ ont réalisé que certains des modèles utilisés dans le projet étaient suffisamment généraux pour être utilisés pour créer un framework de serveur. Le mercredi 27 janvier 1999 (environ un mois après la sortie de JServ 1.0b), Stefano a proposé de démarrer un projet appelé Java Apache Server Framework. Son objectif est de devenir la base de tout le code serveur Java chez Apache. L'idée est de centraliser certains composants et de réutiliser le code entre les projets en fournissant un framework.
Stefano Mazzocchi, Federico Barbieri et Pierpaolo Fumagalli ont créé la version originale. Fin 2000, Berin Loritsch et Peter Donald rejoignent le projet. À cette époque, Pierpaolo et Stefano s'étaient tournés vers le développement d'autres projets et le Java Apache Server Framework commençait à s'appeler Avalon. Ces cinq développeurs sont principalement responsables de la conception et des concepts utilisés dans la version actuelle du framework. La version actuelle est très similaire à la version publiée en juin 2000. En fait, la principale différence réside dans la réorganisation des packages et la division des projets en sous-projets. Les mêmes modèles de conception et interfaces existent toujours aujourd’hui.
Qu'est-ce qu'Avalon ?
Avalon est le projet parent de cinq sous-projets : Framework, Excalibur, LogKit, Phoenix et Cornerstone. Lorsqu'ils entendent Avalon, la plupart des gens pensent à Framework, mais Avalon inclut bien plus que Framework. Avalon a commencé sous le nom de Java Apache Server Framework, composé du framework, des outils, des composants et d'une implémentation principale du serveur, le tout dans un seul projet. Étant donné que les différentes parties d'Avalon ont différents niveaux de maturité et différents cycles de publication, nous avons décidé de diviser Avalon en petits projets mentionnés précédemment. Cela permet également aux nouveaux développeurs de comprendre et d'apprendre plus facilement les différentes parties d'Avalon - ce qui était presque impossible à faire auparavant. Framework
Avalon Framework est la base de tous les autres projets sous l'égide d'Avalon. Il définit les interfaces, les contrats et l'implémentation par défaut d'Avalon. Framework y place la majeure partie du travail, c'est donc aussi le projet le plus mature.
Excalibur
Avalon Excalibur est un ensemble de composants côté serveur que vous pouvez utiliser dans vos propres projets. Il comprend la mise en œuvre du pooling, la gestion des connexions à la base de données et d'autres implémentations de gestion des composants.
LogKit
Avalon LogKit est un ensemble d'outils de journalisation à grande vitesse utilisé par Framework, Excalibur, Cornerstone et Phoenix. Son modèle utilise les mêmes principes que le package JDK 1.4 Logging, mais est compatible avec JDK 1.2+.
Phoenix
Avalon Phoenix est le cœur du serveur, qui gère la publication et l'exécution des services (Services, implémentés en tant que composants côté serveur, appelés Blocs).
Cornerstone
Avalon Cornerstone est un ensemble de blocs ou de services pouvant être déployés dans un environnement Phoenix. Ces blocs incluent la gestion des sockets et la planification des tâches entre les blocs.
Scratchpad
Scratchpad n'est pas vraiment un projet formel, mais une zone de préparation pour les composants qui ne sont pas encore prêts à être intégrés dans Excalibur. La qualité de ces composants varie considérablement et il n'est pas garanti que leurs API restent inchangées jusqu'à ce qu'ils soient promus dans le projet Excalibur.
Points forts de cet aperçu
Dans cet aperçu, nous nous concentrerons sur le framework Avalon, mais aborderons suffisamment Avalon Excalibur et Avalon LogKit pour vous aider à démarrer. Nous utiliserons un serveur d'entreprise hypothétique pour montrer comment utiliser Avalon dans la pratique. Il n'entre pas dans le cadre de cet aperçu de définir une méthodologie complète et exhaustive, ou de présenter tous les aspects de tous les sous-projets. Nous nous concentrons sur le framework Avalon car il constitue la base de tous les autres projets. Si vous pouvez comprendre le cadre, vous pouvez comprendre n'importe quel projet basé sur Avalon. Vous vous familiariserez également avec certains idiomes de programmation (idiomes) couramment utilisés dans Avalon. Une autre raison de se concentrer sur les frameworks et d'impliquer les projets Avalon Excalibur et Avalon LogKit est qu'ils sont officiellement publiés et pris en charge.
Où Avalon peut-il être utilisé ?
On m'a demandé à plusieurs reprises de clarifier à quoi Avalon convient et à quoi il ne convient pas. Avalon se concentre sur la programmation côté serveur et facilite la conception et la maintenance de projets centrés sur les applications serveur. Avalon peut être décrit comme un framework qui inclut une implémentation. Bien qu'Avalon se concentre sur les solutions côté serveur, de nombreuses personnes le trouvent également utile pour les applications générales. Les concepts utilisés dans Framework, Excalibur et LogKit sont suffisamment généraux pour être appliqués dans n'importe quel projet. Cornerstone et Phoenix sont deux projets qui se concentrent plus directement sur les serveurs. Cadre
1. Une structure porteuse ou fermée 2. Un système ou un agencement de base contenant des idées
Le mot framework a un sens large dans les applications. Les cadres axés sur une seule industrie, comme les systèmes pharmaceutiques ou les systèmes de communication, sont appelés cadres de marché verticaux. La raison en est que le même cadre ne convient pas à d’autres secteurs. Un cadre très polyvalent et pouvant être utilisé dans plusieurs secteurs est appelé cadre de marché horizontal. Avalon est un cadre de marché horizontal. Vous pouvez utiliser le framework d'Avalon pour créer un cadre de marché vertical. L'exemple le plus convaincant de framework de marché vertical construit avec Avalon est le framework de publication Apache Cocoon. Apache Cocoon version 2 est construit à l'aide des projets Framework, Excalibur et LogKit d'Avalon. Il tire parti des interfaces et des contrats du Framework, permettant aux développeurs de passer moins de temps à comprendre le fonctionnement de Cocoon. Il utilise également efficacement le code de gestion des sources de données et de gestion des composants fourni par Excalibur afin de ne pas avoir à réinventer la roue. Enfin, il utilise LogKit pour gérer tous les problèmes de journalisation dans le cadre de publication. Une fois que vous avez compris les principes qui sous-tendent Avalon Framework, vous pouvez comprendre n'importe quel système construit sur Avalon. Une fois que vous aurez compris le système, vous serez en mesure de détecter plus rapidement les bugs causés par une mauvaise utilisation du framework. Il n’y a pas de formule magique
Il convient de mentionner que toute tentative d’utiliser un outil comme formule magique pour réussir est source d’ennuis. Avalon ne fait pas exception. Le framework d'Avalon étant conçu pour les solutions côté serveur, son utilisation pour créer des interfaces utilisateur graphiques (GUI) n'est pas une bonne idée. Java dispose déjà d'un framework pour créer des interfaces graphiques appelé Swing. Bien que vous deviez déterminer si Avalon convient à votre projet, vous pouvez toujours apprendre quelque chose de ses principes et de sa conception. La question que vous devez vous poser est : "Où le projet sera-t-il utilisé ?" Si la réponse est qu'il fonctionnera dans un environnement de serveur, alors Avalon serait un bon choix, que vous créiez un servlet Java ou un objectif spécial. Application serveur. Si la réponse est qu'il fonctionnera sur la machine d'un client et n'aura aucune interaction avec le serveur, alors peut-être qu'Avalon n'est pas une bonne solution. Malgré cela, le modèle de composant est très flexible et permet de gérer la complexité des applications volumineuses.
Principes et modèles
Avalon est construit sur la base de certains principes de conception spécifiques. Les deux modes les plus importants sont l’inversion du contrôle et la séparation des préoccupations. La programmation orientée composants, la programmation orientée aspect et la programmation orientée service ont également un impact sur Avalon. Chaque principe de programmation pourrait remplir des volumes de livres, mais ce sont tous des habitudes de design thinking. Inversion de Contrôle
Le concept d'Inversion de Contrôle (IOC) signifie que les composants sont toujours gérés en externe. Cette expression a été utilisée pour la première fois par Brian Foote dans l'un de ses articles. Tout ce dont le composant a besoin lui est fourni via les contextes, les configurations et les enregistreurs. En fait, chaque étape du cycle de vie d'un composant est contrôlée par le code qui crée le composant. Lorsque vous utilisez ce modèle, vous implémentez une méthode permettant aux composants d'interagir en toute sécurité avec votre système. IOC n'est pas synonyme de sécurité ! IOC fournit un mécanisme qui permet d'implémenter un modèle de sécurité extensible. Pour qu'un système soit véritablement sécurisé, chaque composant doit être sécurisé, aucun composant ne peut modifier le contenu des objets qui lui sont transmis et toutes les interactions doivent utiliser des entités connues. La sécurité est une préoccupation majeure et IOC est un outil dans l'arsenal d'un programmeur pour atteindre ses objectifs de sécurité.
Séparation des préoccupations
L'idée selon laquelle vous devez examiner votre système sous différents angles a conduit au modèle de séparation des préoccupations (SOC). Un exemple consiste à examiner un serveur Web sous différents angles sur le même espace problématique. Le serveur Web doit être sécurisé, stable, gérable, configurable et répondre aux spécifications HTTP. Chaque attribut est une considération distincte. Certaines de ces considérations sont liées à d’autres, comme la sécurité et la stabilité (si un serveur est instable, il ne peut pas être sécurisé). Le modèle de considération séparé a à son tour conduit à la programmation orientée aspect (AOP). Les chercheurs ont découvert que de nombreuses considérations ne peuvent pas être prises en compte au niveau de la granularité de la classe ou de la méthode. Ces considérations sont appelées aspects. Des exemples d'aspects incluent la gestion des cycles de vie des objets, la journalisation, la gestion des exceptions, ainsi que le nettoyage et la libération des ressources. Puisqu'il n'existe pas d'implémentation AOP stable, l'équipe de développement d'Avalon a choisi d'implémenter certains aspects ou considérations en fournissant quelques petites interfaces qui sont ensuite implémentées par des composants.
Programmation orientée composants
La programmation orientée composants (Component Oriented Programming, COP) est une idée de division du système en certains composants ou installations. Chaque installation dispose d'une interface de travail et d'un contrat entourant cette interface. Cette approche permet de remplacer facilement les instances de composants sans affecter le code dans d'autres parties du système. La principale différence entre la programmation orientée objet (POO) et COP est le niveau d'intégration. La complexité des systèmes COP est plus facile à gérer, grâce à moins d'interdépendances entre les classes. Cela augmente le degré de réutilisation du code. L'un des principaux avantages du COP est que la modification de certaines parties du code du projet ne brise pas l'ensemble du système. Un autre avantage est que vous pouvez avoir plusieurs implémentations d'un composant et les sélectionner au moment de l'exécution.
Programmation orientée services
L'idée de la programmation orientée services (SOP) est de diviser le système en certains services fournis par le système. Service
1. Travaux ou tâches effectués pour des tiers 2. Une installation qui assure la réparation ou l'entretien 3. Une installation qui fournit des outils au public
Avalon's Phoenix considère chaque installation à fournir comme un service consistant en un interface et contrats associés. La mise en œuvre d'un service s'appelle un Bloc. Il est important de comprendre qu'un programme serveur est composé de plusieurs services. En prenant un serveur de messagerie comme exemple, il disposera de services de traitement de protocole, de services d'authentification et d'autorisation, de services de gestion et de services de traitement de courrier de base. Avalon's Cornerstone fournit des services de bas niveau dont vous pouvez profiter dans votre propre système. Les services fournis incluent la gestion des connexions, la gestion des sockets, la gestion et la planification des participants/rôles, etc. Nous introduisons ici les services car ils se rapportent au processus de décomposition de notre système hypothétique en différentes installations.
Décomposer un système
Comment décidez-vous de ce qui compose un composant ? La clé est de définir les installations dont votre solution a besoin pour fonctionner efficacement.
Nous utiliserons un serveur métier hypothétique pour montrer comment identifier et déterminer les services et les composants. Après avoir défini certains services utilisés par le système, nous prendrons l'un de ces services comme exemple et définirons les différents composants requis pour ce service. Mon objectif est de vous transmettre quelques concepts qui vous aideront à définir votre système en éléments gérables.
Analyse du système - Identification des composants
Bien qu'il dépasse le cadre de cet article de fournir une méthodologie complète et exhaustive, j'aimerais discuter de quelques questions. Nous commencerons par des définitions des composants et des services orientées vers la mise en œuvre, puis fournirons une définition pratique. Composant
Un composant est une combinaison d'une interface de travail et d'une implémentation de cette interface de travail. L'utilisation de composants fournit un couplage lâche entre les objets, permettant de modifier l'implémentation sans affecter le code qui l'utilise.
Service
Un service se compose d'un ou plusieurs composants, fournissant une solution complète. Des exemples de services incluent les gestionnaires de protocoles, les planificateurs de tâches, les services d'authentification et d'autorisation, etc.
Bien que ces définitions fournissent un point de départ, elles ne fournissent pas une image complète. Afin de décomposer un système (défini comme un ensemble d'installations qui composent un projet) en ses composants nécessaires, je recommande d'utiliser une approche descendante. Cette approche évite de s'enliser dans les détails avant de savoir exactement ce qui est disponible. Déterminez la portée de votre projet
Quelles fonctions votre projet est censé accomplir, vous devriez toujours avoir une idée générale au début. Dans le monde des affaires, un premier énoncé de travail fait l’affaire. Dans le monde open source, cela se fait généralement avec une idée ou un processus de brainstorming. Je pense qu’il est important d’avoir une vue d’ensemble d’un projet. Il est évident qu’un grand projet sera composé de nombreux services différents, alors qu’un petit projet ne comportera qu’un ou deux services. Si vous commencez à vous sentir un peu dépassé, rappelez-vous simplement que les grands projets sont en réalité de nombreux petits projets regroupés sous un même toit. Finalement, vous serez en mesure de comprendre la vue d’ensemble de l’ensemble du système.
Description du travail : Business Server
Business Server (Business Server) est un projet hypothétique. Aux fins de notre discussion, sa fonctionnalité consiste à traiter les commandes clients, à automatiser la facturation aux clients et à gérer le contrôle des stocks. Les commandes clients doivent être traitées dès leur arrivée, via un certain type de système de transaction. Le serveur émet automatiquement une facture au client 30 jours après l'exécution de la commande client. L'inventaire est géré à la fois par le serveur et par les niveaux de stock actuels dans l'usine ou l'entrepôt. Le serveur d'entreprise sera un système distribué et chaque serveur communiquera avec d'autres serveurs via un service de messagerie.
Découverte des services
Nous utiliserons ce projet Business Server pour découvrir des services. Compte tenu de l'énoncé des travaux trop général ci-dessus, on peut immédiatement voir certaines des prestations définies dans la description du projet. La liste des services peut être divisée en deux grandes catégories : les services explicites (services pouvant être dérivés directement de la description de travail) et les services implicites (services découverts sur la base de travaux similaires ou services prenant en charge des services explicites). Notez que l'entreprise qui met en œuvre le système n'est pas obligée de développer elle-même tous les services : certains peuvent être achetés en tant que solutions commerciales. Dans ces cas-là, nous pourrions développer un wrapper qui nous permettrait d’interagir de manière déterministe avec des produits commerciaux. L'entreprise mettant en œuvre le système créera la plupart des services. Services explicites
À partir de la description de travail, nous pouvons exporter rapidement certains services. Mais cette première analyse ne signifie pas que notre travail est terminé, car la définition de certains services nécessite l'existence d'autres services pour être assurée. Services de traitement des transactions
La description de poste indique clairement que « les commandes doivent être traitées dès leur arrivée ». Cela suggère que nous avons besoin d’un mécanisme pour accepter les demandes de vente et les traiter automatiquement. Ceci est similaire au fonctionnement des serveurs Web. Ils reçoivent une demande de ressource, la traitent et renvoient un résultat (comme une page HTML). C'est ce qu'on appelle le traitement des transactions. Pour être complet, il existe différents types de transactions. Ce service général de traitement des transactions devra très probablement être décomposé en quelque chose de plus spécifique, comme un « processeur de commandes clients ». La méthode exacte dépend de la généralité de votre service. Il existe un équilibre entre la convivialité et la réutilisabilité. Plus un service est générique, plus il est réutilisable. C’est aussi généralement plus difficile à comprendre.
Service de planification
Dans certains cas, lorsqu'une transaction est complétée, après une période de temps déterminée, un événement doit être programmé. De plus, le processus de contrôle des stocks doit pouvoir émettre des bons de commande périodiquement. Parce que la description de poste indique que « 30 jours après l'exécution de la commande client, le serveur émet automatiquement une facture au client », nous avons besoin d'un service de planification. Heureusement, Avalon Cornerstone nous en fournit un afin que nous n'ayons pas à en écrire un nous-mêmes.
Service de messagerie
La description de travail indique que dans notre système distribué "chaque serveur communiquera avec d'autres serveurs via un service de messagerie". Pensons-y, parfois les utilisateurs veulent un produit spécifique ou une méthode qu'ils souhaitent utiliser. Les services de messagerie sont un excellent exemple de valorisation des produits d'autres entreprises. Très probablement, nous utiliserons Java Messaging Service (JMS) comme interface de Messaging Service. JMS étant un standard, il est peu probable que son interface change de sitôt. D'après l'expérience pratique, un système orienté message bien défini est plus évolutif qu'un système orienté objet (tel que les EJB). L’une des raisons d’une meilleure évolutivité est que les messages ont généralement une charge de mémoire simultanée moindre. Une autre raison est qu'il est plus facile de répartir la charge de traitement des messages sur tous les serveurs, plutôt que de concentrer tout le traitement sur un petit cluster de serveurs (ou même sur un seul serveur).
Service de contrôle des stocks
Bien qu'il ne s'agisse pas d'un service classique, il s'agit d'une exigence pour ce système. Le service de contrôle des stocks surveille en permanence les enregistrements des stocks d'usine ou d'entrepôt et déclenche des événements lorsque les stocks commencent à s'épuiser.
Services implicites
En utilisant l'expérience acquise dans les systèmes précédents pour décomposer davantage le système en d'autres services, nous obtiendrons certains services qui ne sont pas explicitement signalés mais qui sont nécessaires au système. En raison de contraintes d'espace, nous ne ferons pas une ventilation complète. Services d'authentification et d'autorisation
Les services d'authentification et d'autorisation ne sont pas explicitement mentionnés dans la description de poste, mais tous les systèmes d'entreprise doivent sérieusement prendre en compte la sécurité. Cela signifie que tous les clients du système doivent être authentifiés et que toutes les actions des utilisateurs doivent être autorisées.
Services d'automatisation des flux de travail
L'automatisation des flux de travail est un domaine de développement populaire dans les systèmes d'entreprise. Si vous n'utilisez pas de serveur de gestion de flux de travail tiers, vous devrez en écrire un vous-même. En règle générale, l'automatisation des flux de travail consiste à utiliser des systèmes logiciels pour planifier des tâches tout au long des processus métier d'une entreprise. Pour plus d'informations, veuillez consulter le site Web du Workflow Management Council à l'adresse http://www.wfmc.org/.
Service Centre de documents
En tant qu'informations sur l'état actuel d'une tâche, la définition du terme « Centre de documents » est très imprécise. En d’autres termes, lorsqu’une entreprise reçoit un bon de commande, notre système doit être capable de stocker et de rappeler les informations du bon de commande. La facturation a les mêmes exigences que tout autre processus du système, de l'inventaire aux demandes de nouveaux utilisateurs.
Résumé
J'espère que les exemples de services du projet Business Server pourront vous aider à en découvrir davantage. Vous constaterez qu'à mesure que vous passez des couches d'abstraction supérieures aux couches inférieures, vous constaterez que davantage de types de services sont requis, comme un service de connexion pour gérer les demandes sur un port ouvert. Certains des services que nous définissons seront mis en œuvre via des systèmes tiers, tels que les services de messagerie et les services de gestion de flux de travail. Pour ces services, vous avez tout intérêt à utiliser une interface standard afin de pouvoir changer de fournisseur ultérieurement. Certains services sont en réalité de grands services composés de plusieurs services. Certains services sont déjà fournis dans Avalon Excalibur ou Avalon Cornerstone. Une chose que vous devez garder à l’esprit lors de la découverte de services dans un système est qu’un service doit être un sous-système de haut niveau. Cela vous aidera à définir les composants grâce à une équipe d’analystes. Parce que nous avons identifié les principaux services, vous pouvez demander à plusieurs personnes (ou équipes) de décomposer chaque service en parallèle. Les limites des sous-systèmes sont également bien définies et il y a peu de risques de chevauchement. Si vous décidez de faire une analyse parallèle, vous devez revenir en arrière et identifier les composants communs afin qu'ils puissent être réutilisés autant que possible.
Diagramme UML pour le serveur d'entreprise
Découverte des composants
Nous utiliserons le service Document Center mentionné précédemment comme exemple pour illustrer le processus d'identification des composants appropriés. Par souci de discussion, nous énumérons maintenant les exigences relatives aux services Document Center. Le Centre de documents utilisera une base de données comme stockage persistant, autorisera les clients et mettra en cache les documents en mémoire. Définition pratique des composants
Lorsque nous parlons de composants, vous devez y penser en termes de : De quelles installations mon service a-t-il besoin pour fonctionner ? Avalon croit au concept de création d'un système. Les développeurs d'un système sont confrontés à une liste de responsabilités pour un composant, appelée son rôle. Qu'est-ce qu'un rôle ?
La notion de rôle vient du théâtre. Une pièce de théâtre, une comédie musicale ou un film aura un certain nombre de personnages joués par des acteurs. Même si les acteurs ne semblent jamais manquer, le nombre de rôles est limité. Le scénario d'une série définit la fonction ou le comportement d'un personnage. Tout comme ce qui se passe au théâtre, le script détermine la manière dont vous interagissez avec les composants. Pensez aux différents acteurs du système. Vous projeterez les composants dans les acteurs et leur parlerez. Un rôle est un contrat pour une classe de composants. Par exemple, notre service Document Center nécessite l’exploitation d’une base de données. Avalon Excalibur définit un composant qui répond aux besoins du rôle « Data Source ». Il existe deux composants différents dans Excalibur, répondant tous deux aux besoins de ce rôle. Le choix à utiliser dépend de l'environnement dans lequel se trouve le service, mais ils satisfont tous au même contrat. De nombreux systèmes basés sur Avalon n'utiliseront qu'un seul composant actif par personnage. Les scripts sont des interfaces de travail : des interfaces avec lesquelles d'autres composants interagissent. Lors de la détermination de l’interface d’un composant, vous devez avoir un contrat précis et le garder à l’esprit. Le contrat précise ce que les utilisateurs du composant doivent fournir et ce que produit le composant. Parfois, la sémantique d’utilisation doit être incluse dans le contrat. Un exemple est la différence entre les composants de stockage temporaires et les composants de stockage persistants. Une fois les interfaces et les protocoles définis, vous pouvez travailler à leur mise en œuvre.
Qu'est-ce qui fait un bon composant candidat ?
Dans notre service Document Center, nous avons identifié quatre composants possibles : DataSourceComponent (d'Excalibur), Cache, Repository, Guardian. Vous devez rechercher des rôles susceptibles d’avoir plusieurs implémentations avec lesquelles l’interaction peut se produire de manière transparente. A travers cet exemple, vous verrez qu’il existe des situations où vous devez utiliser des installations alternatives. Dans la plupart des cas, vous n'utiliserez qu'une seule implémentation de cette fonctionnalité, mais vous devez pouvoir la mettre à niveau indépendamment sans affecter le reste du système. Dans d'autres cas, vous devez utiliser différentes implémentations en fonction de l'environnement. Par exemple, la « source de données » définie par Excaliber gérera généralement elle-même tout le pool de connexions JDBC, mais vous souhaiterez parfois profiter des fonctionnalités fournies dans Java 2 Enterprise Edition (J2EE). Excalibur résout ce problème en permettant à un composant « Source de données » de gérer directement les connexions et les pools JDBC, et à un autre composant utilisant l'interface de nommage et d'annuaire (JNDI) de Java pour obtenir des connexions spécifiques.
Qu'est-ce qui n'est pas un bon composant ?
Les personnes habituées à utiliser des JavaBeans aiment tout implémenter en tant que JavaBean. Cela signifie tout, des modèles de données au traitement des transactions. Si vous abordez les composants de cette façon, vous risquez de vous retrouver avec un système trop complexe. Considérez un composant comme un modèle de service ou d'installation plutôt que comme un modèle de données. Vous pouvez avoir des composants qui extraient des données d'autres sources, mais les données doivent toujours rester des données. Un exemple de cette philosophie dans Avalon Excalibur est que la connexion n'est pas un composant. Un autre exemple est le composant Guardian que nous avons mentionné plus tôt. On pourrait faire valoir que la logique contenue dans Guardian est trop pertinente pour le service Document Centre et ne peut pas être utilisée comme composant dans un service complètement différent. Bien qu'il existe de nombreuses façons de gérer la complexité et de la rendre flexible, cela n'en vaut parfois pas la peine. Dans ce cas, vous devez peser soigneusement votre décision. Si la logique d'un composant potentiel doit être appliquée de manière cohérente, il peut être judicieux de le traiter comme un composant. Il peut y avoir plusieurs instances d'un composant dans un système et elles peuvent être sélectionnées au moment de l'exécution. Si la logique du composant sous-jacent est déterminée uniquement par un autre composant, il peut être possible de placer la logique dans cet autre composant. À travers les exemples du composant Guardian et du composant Repository, nous pouvons affirmer que Guardian est trop concentré sur le Repository et n'est pas implémenté en tant que composant.
Décomposition du service du Centre de documentation
Nous listerons les composants qui seront implémentés, ainsi que leurs rôles, causes profondes et sources (si les composants existent déjà). DocumentRepository
DocumentRepository est le composant parent de l'ensemble du service. Dans Avalon, les services sont implémentés sous forme de blocs, qui sont des composants d'un type spécifique. Le bloc doit avoir une interface de travail qui étend l’interface du marqueur de service. L'interface Block étend également l'interface Component d'Avalon. Veuillez noter que Block et Service sont des interfaces incluses dans Avalon Phoenix. Enfin, le Service reste techniquement un type spécifique de Composant. DocumentRepository est la façon dont nous obtenons les objets Document à partir du stockage persistant. Il interagit avec d'autres composants du service pour assurer la sécurité, la fonctionnalité et la vitesse. Ce DocumentRepository spécifique se connectera à la base de données et utilisera la logique de la base de données en interne pour créer des objets Document.
DataSourceComponent
DataSourceComponent est fourni par Avalon Excalibur. C'est ainsi que nous obtenons un objet de connexion JDBC valide.
Cache
Le cache est une installation de stockage en mémoire à court terme. DocumentRepository l'utilisera pour enregistrer les objets Document et les référencer via un algorithme de hachage. Afin d'améliorer la réutilisation des composants Cache, les objets stockés doivent implémenter une interface Cacheable.
Gardien
Le rôle du composant Guardian est basé sur les autorisations de gestion des participants. Guardian chargera l'ensemble de règles de licence à partir de la base de données. Guardian utilisera le modèle de sécurité Java standard pour garantir l'accès à un document spécifique.
Résumé
À présent, vous devriez avoir une idée de ce qui constitue un bon composant. L'exemple décrit tous les composants du service Document Center et présente brièvement le travail qu'ils vont accomplir. Un rapide coup d'œil à cette liste illustre l'approche consistant à mettre en œuvre les installations en tant que composants plutôt que données. À présent, vous devriez être en mesure de déterminer les composants dont votre service a besoin pour fonctionner.
Cadre et fondation
Nous décrirons les contrats et les interfaces d'Avalon pour jeter les bases nous permettant d'écrire réellement des composants.
Avalon Framework est la partie centrale de tout le projet Avalon. Si vous comprenez les contrats et les structures définis par un framework, vous pouvez comprendre n'importe quel code qui exploite ce framework. Rappelez-vous les principes et les modèles dont nous avons discuté. Dans cette section, nous expliquons en détail comment fonctionne le concept de rôles dans la pratique, le cycle de vie des composants et le fonctionnement des interfaces.
Définir le rôle d'un composant
Dans Avalon, tous les composants jouent un rôle. La raison est que vous obtenez vos composants via des rôles. Dans ce domaine, la seule chose à considérer est la signature du personnage. Rappelez-vous de la partie 2 que nous avons défini un composant comme « une combinaison d'une interface de travail et d'une implémentation de cette interface de travail ». L'interface de travail est le rôle. Création d'une interface pour un rôle
Vous verrez ci-dessous un exemple d'interface, ainsi que quelques bonnes pratiques et pourquoi. package org.apache.bizserver.docs;interface publique DocumentRepository extends Component{ String ROLE = DocumentRepository.class.getName(); Document getDocument(Principal requestor, int refId);}
Meilleure pratique
· Contient un nom A chaîne pour "ROLE", qui est le nom officiel du rôle. Ce nom est identique au nom complet de l'interface de travail. Cela nous aidera à l'avenir lorsque nous aurons besoin d'obtenir une instance d'un composant. · Si possible, veuillez étendre l'interface du composant. Cela facilitera la tâche lors de la publication de vos composants. Cela ne vous est d'aucune utilité si vous n'êtes pas responsable du contrôle de l'interface de travail. Ce n'est pas trop un problème puisque vous pouvez toujours le convertir en une instance de Component lors de la publication. · Faites une chose et faites-la bien. L'interface d'un composant doit être aussi simple que possible. Si votre interface de travail étend une autre interface, cela rendra le contrat du composant difficile à comprendre. Un vieil acronyme américain le dit bien : Keep It Simple, Stupid (KISS). Ce n'est pas difficile d'être plus intelligent (et plus stupide) que soi, je l'ai moi-même fait plusieurs fois. · Identifiez uniquement les méthodes dont vous avez besoin. Le programme client ne doit connaître aucun détail d’implémentation, et trop de méthodes alternatives ne feront qu’introduire une complexité inutile. En d’autres termes, choisissez une voie et respectez-la. · Ne laissez pas l'interface de votre personnage prolonger un cycle de vie ou une interface de survie. Si vous implémentez une telle classe ou interface, vous essayez d’implémenter la spécification. Ce n'est pas un bon modèle et ne fera que provoquer des problèmes de débogage et d'implémentation à l'avenir.
Choisissez un nom de personnage
Dans Avalon, chaque personnage a un nom. C'est ainsi que vous obtenez des références à d'autres composants du système. L'équipe de développement d'Avalon a défini certaines conventions pour nommer les personnages. Convention de dénomination
· Le nom complet de l'interface de travail est généralement le nom du rôle. Les exceptions sont énumérées sous ces règles générales. Dans cet exemple, le nom de notre composant théorique doit être « org.apache.bizserver.docs.DocumentRepository ». C'est le nom qui doit être inclus dans l'attribut "ROLE" de votre interface. · Si nous obtenons une référence au composant via un sélecteur de composant, nous utilisons généralement le nom de rôle déduit de la première règle, plus le mot « Sélecteur » à la fin. Le résultat de cette règle de dénomination sera « org.apache.bizserver.docs.DocumentRepositorySelector ». Vous pouvez obtenir ce nom via DocumentRepository.ROLE + "Selector". · Si nous avons plusieurs composants qui implémentent la même interface de travail mais servent des objectifs différents, nous séparerons les rôles. Un rôle est le but d'un composant dans un système. Chaque nom de personnage commencera par le nom du personnage d'origine, mais le nom du personnage sera ajouté sous la forme /${Purpose}. Par exemple, pour DocumentRePository, nous pouvons avoir les objectifs suivants : PurchaseOrder (bon de commande) et Bill (facture). Ces deux rôles peuvent être exprimés respectivement par DocumentRepository.ROLE + "/PurchaseOrder" et DocumentRepository.ROLE + "/Bill".
Présentation de l'interface du framework
L'ensemble du framework Avalon peut être divisé en sept catégories principales (selon l'API) : activité, composant, configuration, contexte, enregistreur, paramètres, thread et divers. Chaque catégorie (sauf Divers) représente un domaine de préoccupation. Un composant implémente généralement plusieurs interfaces pour indiquer le sens de considération qui l'intéresse. Cela permet au conteneur de composants de gérer chaque composant de manière cohérente. Cycle de vie de l'interface Avalon
Lorsqu'un framework implémente plusieurs interfaces pour considérer différents aspects du composant séparément, il existe un risque de confusion dans l'ordre des appels de méthode. Le framework Avalon en est conscient, c'est pourquoi nous avons développé un protocole pour l'ordre du cycle de vie des événements. Si votre composant n'implémente pas l'interface appropriée, il passe simplement à l'événement suivant à gérer. Puisqu'il existe une manière correcte de créer et de préparer des composants, vous pouvez configurer le composant lorsque l'événement est reçu. Le cycle de vie d'un composant est divisé en trois phases : la phase d'initialisation, la phase de service d'activité et la phase de destruction. Étant donné que ces étapes se déroulent de manière séquentielle, nous discuterons de ces événements dans l’ordre. De plus, en raison du langage Java, le comportement de construction et de finalisation est effectué de manière implicite, nous l'ignorerons donc. Nous listerons le nom de la méthode et l’interface requise. Dans chaque phase, il y a des étapes identifiées par des noms de méthodes. Si le composant étend l'interface spécifiée entre parenthèses, ces étapes sont effectuées séquentiellement. Phase d'initialisation
Les étapes suivantes se déroulent séquentiellement et ne se produisent qu'une seule fois pendant la durée de vie du composant. 1. EnableLogging() [LogEnabled] 2. contextualize() [Contextualisable] 3. compose() [Composable] 4. configure() [Configurable] ou Parameterize() [Paramétrable] 5. initialize() [Initialisable] 6. start () [Démarrable]
Phase de service actif
Les étapes suivantes se produisent séquentiellement, mais peuvent se produire plusieurs fois au cours de la durée de vie du composant. Notez que si vous choisissez de ne pas implémenter l'interface Suspendable, il est de la responsabilité de votre composant de garantir un fonctionnement correct lors de l'exécution des étapes commençant par re. 1. suspend() [Suspendable] 2. recontextualize() [Recontextualisable] 3. recompose() [Recomposable] 4. reconfigure() [Reconfigurable] 5. curriculum vitae() [Suspendable]
Phase de destruction
suivant Le les étapes se produisent séquentiellement et une seule fois pendant la durée de vie du composant. 1. stop() [Startable] 2. dispose() [Jetable]
Contrat-cadre Avalon
Dans cette section, nous couvrirons tout par ordre alphabétique, sauf la partie la plus importante : Composant, Nous mettons il à l'avant. Lorsque j'utilise « conteneur » ou « conteneur » pour décrire des composants, j'ai une signification particulière. Je veux dire les composants enfants qui ont été instanciés et contrôlés par le composant parent. Je ne parle pas des composants que vous obtenez via ComponentManager ou ComponentSelector. De plus, certaines commandes d'exécution d'étape Avalon reçues par le composant conteneur doivent être propagées à tous ses sous-composants, du moment que ces sous-composants implémentent l'interface correspondante. Les interfaces spécifiques sont Initialisables, Démarrables, Suspendables et Jetables. La raison pour laquelle les contrats sont organisés de cette manière est que ces interfaces ont des conventions d'exécution particulières. Composant
C'est le cœur d'Avalon Framework. L'interface définie par cette direction de considération lancera ComponentException. Component
Chaque composant Avalon doit implémenter l'interface Component. Le gestionnaire de composants et le sélecteur de composants ne traitent que des composants. Cette interface n'a pas de méthodes définies. Il sert simplement d'interface de jeton. Tout composant doit utiliser le constructeur par défaut sans paramètres. Toute configuration se fait via l'interface Configurable ou Paramétrable.
Composable
Un composant qui utilise d'autres composants doit implémenter cette interface. Cette interface n'a qu'une seule méthode compose(), qui prend un seul paramètre de type ComponentManager. Le contrat entourant cette interface est que compose() est appelé une et une seule fois pendant la durée de vie du composant. Cette interface, comme toute autre interface définissant des méthodes, utilise le modèle de contrôle inverse. Il est appelé par le conteneur du composant et seuls les composants requis par le composant apparaîtront dans le ComponentManager.
Recomposable
Dans de rares cas, un composant nécessitera un nouveau ComponentManager et un nouveau mappage de rôle de composant. Dans ces cas, l'interface recomposable doit être implémentée. Son nom de méthode est également différent de celui de Composable, qui est recompose(). Le contrat autour de cette interface est que la méthode recompose() peut être appelée un nombre illimité de fois, mais pas avant que le composant ne soit complètement initialisé. Lorsque cette méthode est appelée, le composant doit se mettre à jour de manière sûre et cohérente. Cela signifie généralement que toutes les opérations effectuées par le composant doivent s'arrêter entre les mises à jour et reprendre après les mises à jour.
Activité
Cet ensemble d'interfaces est lié au contrat du cycle de vie des composants. Si une erreur se produit lors de cet ensemble d’appels d’interface, vous pouvez lancer une exception générique. Jetable
Si un composant a besoin de savoir de manière structurée qu'il n'est plus nécessaire, il peut utiliser l'interface jetable. Une fois qu'un composant est désalloué, il ne peut plus être utilisé. En fait, il n’attend que d’être ramassé. Cette interface n'a qu'une seule méthode dispose(), qui n'a aucun paramètre. Le contrat entourant cette interface est que la méthode dispose() est appelée une fois et est la dernière méthode appelée pendant la durée de vie du composant. Cela indique également que le composant ne sera plus utilisé et que les ressources occupées par le composant doivent être libérées.
Initialisable
Si un composant doit créer d'autres composants ou doit effectuer des opérations d'initialisation pour obtenir des informations à partir d'autres étapes d'initialisation, il doit utiliser l'interface Initialisable. Cette interface n'a qu'une seule méthode initialize(), qui n'a aucun paramètre. Le contrat entourant cette interface est que la méthode initialize() est appelée une fois, et c'est la dernière méthode appelée pendant le processus d'initialisation. Cela indique également que le composant est actif et peut être utilisé par d'autres composants du système.
Startable
Si un composant continue de s'exécuter pendant sa durée de vie, il doit utiliser l'interface Startable. Cette interface définit deux méthodes : start() et stop(). Les deux méthodes n'ont aucun paramètre. Le contrat entourant cette interface est que la méthode start() est appelée une fois une fois le composant complètement initialisé. La méthode stop() est appelée une fois avant la destruction du composant. Aucun d’entre eux n’est appelé deux fois. start() est toujours appelé avant stop(). Les implémentations de cette interface sont nécessaires pour exécuter les méthodes start() et stop() en toute sécurité (contrairement à la méthode Thread.stop()) et sans provoquer d'instabilité du système.
Suspendable
Si un composant permet d'être suspendu au cours de sa durée de vie, il doit utiliser l'interface Suspendable. Bien qu'il soit généralement toujours utilisé avec l'interface Startable, cela n'est pas obligatoire. Cette interface dispose de deux méthodes : suspend() et curriculum vitae(). Les deux méthodes n'ont aucun paramètre. Le contrat entourant cette interface est le suivant : suspend() et summary() peuvent être appelés un nombre illimité de fois, mais pas avant que le composant ne soit initialisé et démarré, ou après que le composant soit arrêté et détruit. L’appel de la méthode suspend() sur un composant suspendu ou l’appel de curriculum vitae() sur un composant déjà en cours d’exécution n’aura aucun effet.
Configuration
Cet ensemble d'interfaces décrit les considérations de configuration. Si un problème survient, tel que le fait de ne pas avoir l'élément de configuration requis, une exception ConfigurationException peut être levée. Configurable
Les composants qui doivent déterminer leur comportement en fonction de la configuration doivent implémenter cette interface pour obtenir une instance de l'objet Configuration. Cette interface possède une méthode configure() avec un seul paramètre de type Configuration. Le contrat entourant cette interface est que la méthode configure() est appelée une fois pendant la durée de vie du composant. L’objet Configuration transmis ne doit pas être nul.
Configuration
L'objet Configuration est un arbre composé d'éléments de configuration, qui ont certaines propriétés. D'une certaine manière, vous pouvez considérer l'objet de configuration comme un DOM grandement simplifié. Il y a trop de méthodes dans la classe Configuration pour être présentées dans cet article. Veuillez vous référer à la documentation JavaDoc. Vous pouvez obtenir une valeur String, int, long, float ou boolean à partir de l'objet Configuration. Si la configuration n'existe pas, une valeur par défaut sera fournie. Il en va de même pour les attributs. Vous pouvez également obtenir des objets de configuration enfants. Le contrat stipule qu'un objet Configuration avec une valeur ne doit avoir aucun objet enfant, et il en va de même pour les objets enfants. Vous remarquerez que vous ne pouvez pas obtenir l'objet Configuration parent. C'est ce que fait le design. Afin de réduire la complexité de la configuration du système, dans la plupart des cas, le conteneur transmettra l'objet de configuration enfant au composant enfant. Les composants enfants ne doivent pas avoir accès aux valeurs de configuration parent. Cette approche peut apporter certains inconvénients, mais l'équipe d'Avalon choisit toujours de donner la priorité à la sécurité lorsque des compromis sont nécessaires.
Reconfigurable
Les composants qui implémentent cette interface se comportent de manière très similaire aux composants recomposables. Il n’a qu’une seule méthode reconfigure(). Cette décision de conception vise à réduire la difficulté d'apprentissage de ces interfaces commençant par Re. Reconfigurable est au Configurable ce que Recomposable est au Composable.
Context
Le concept de Context dans Avalon est né de la nécessité d'un mécanisme pour transmettre des objets simples du conteneur au composant. Les liaisons exactes de protocole et de nom sont intentionnellement indéfinies pour offrir une flexibilité maximale aux développeurs. Le contrat entourant l'utilisation des objets Context est défini par vous dans votre système, bien que les mécanismes soient les mêmes. L'interface Context
Context ne définit qu'une seule méthode get(). Il a un paramètre de type Object et renvoie un objet avec le paramètre object comme valeur clé. L'objet Contexte est assemblé par le conteneur puis transmis au sous-composant. Le sous-composant dispose uniquement d'autorisations de lecture sur le Context. Il n'y a pas d'autre contrat sauf que Context est toujours en lecture seule pour les composants enfants. Si vous étendez le contexte d'Avalon, veillez à respecter ce contrat. Cela fait partie du modèle de contrôle inversé et de la conception de la sécurité. De plus, il n'est pas bon de passer une référence au conteneur dans le Context, pour la même raison que le Context doit être en lecture seule.
Contextualisable
Les composants qui souhaitent recevoir des objets Context du conteneur doivent implémenter cette interface. Il a une méthode nommée contextualize() et le paramètre est l'objet Context assemblé par le conteneur. Le contrat entourant cette interface est que contextualize() est appelé une fois pendant la durée de vie du composant, après LogEnabled, mais avant les autres méthodes d'initialisation.
Recontextualisable
Les composants qui implémentent cette interface se comportent de manière très similaire aux composants recomposables. Il n’a qu’une seule méthode appelée recontextualize(). Cette décision de conception vise à réduire la difficulté d’apprentissage des interfaces commençant par Re. Recontextualisable est à Contextualisable ce que Recomposable est à Composable.
Résolvable
L'interface Résolvable est utilisée pour identifier les objets qui doivent être résolus dans certains contextes. Un exemple est : un objet est partagé par plusieurs objets Context et modifie son propre comportement en fonction d'un contexte spécifique. Le contexte appelle la méthode solve() avant que l'objet ne soit renvoyé.
Logger
Chaque système doit avoir la capacité d'enregistrer les événements. Avalon utilise son projet LogKit en interne. Bien que LogKit dispose de certaines méthodes pour accéder de manière statique à une instance Logger, le Framework s'attend à utiliser le modèle de contrôle inverse. LogEnabled
Chaque composant qui nécessite une instance Logger doit implémenter cette interface. Cette interface possède une méthode nommée activateLogging() qui transmet l'instance d'Avalon Framework Logger au composant. Le contrat autour de cette interface est qu'elle n'est appelée qu'une seule fois pendant la durée de vie du composant, avant toute autre étape d'initialisation.
Logger
L'interface Logger est utilisée pour extraire différentes bibliothèques de journaux. Il fournit une API client unique. Avalon Framework fournit trois classes d'encapsulation qui implémentent cette interface : LogKitLogger pour LogKit, Log4jLogger pour Log4J et Jdk14Logger pour le mécanisme de journalisation JDK1.4.
Paramètres
Avalon reconnaît que la hiérarchie des objets de configuration est trop lourde dans de nombreuses situations. Par conséquent, nous proposons un objet Parameters pour fournir une alternative à l’objet Configuration, en utilisant une approche par paire nom-valeur. Paramétrable
Tout composant souhaitant utiliser des paramètres au lieu d'objets de configuration implémentera cette interface. Parameterizing n'a qu'une seule méthode appelée Parameterize(), le paramètre étant l'objet Parameters. Le contrat entourant cette interface est qu'elle est appelée une fois pendant la durée de vie du composant. Cette interface est incompatible avec l'interface Configurable.
Parameters
L'objet Parameters fournit un mécanisme pour obtenir une valeur via un nom de type String. Il existe des méthodes pratiques qui vous permettent d'utiliser la valeur par défaut si la valeur n'existe pas, ou vous pouvez obtenir n'importe quelle valeur dans le même format à partir de l'interface configurable. Malgré les similitudes entre les objets Parameters et les objets java.util.Property, il existe des différences sémantiques importantes. Premièrement, les paramètres sont en lecture seule. Deuxièmement, les paramètres sont toujours facilement exportés depuis l'objet Configuration. Enfin, l'objet Parameters est exporté à partir du fragment XML et ressemble à ceci :
Thread
Les marqueurs de balise de fil sont utilisé pour signaler des informations sémantiques de base sur le conteneur en fonction de l'utilisation des composants. Ils prennent en compte la sécurité des threads et fournissent des balises pour les implémentations de composants. La meilleure pratique consiste à différer l’implémentation de ces interfaces jusqu’à la classe qui implémente finalement le composant. Cela évite les complications lorsqu'un composant est marqué comme ThreadSafe, mais que les implémentations de composants qui en dérivent ne sont pas thread-safe. Les interfaces définies dans ce package font partie de ce que nous appelons la famille d'interfaces LifeStyle. Une autre interface LifeStyle fait partie du package Excalibur (c'est donc une extension de cet ensemble d'interfaces de base), Poolable est défini dans l'implémentation du pool d'Excalibur. SingleThreaded
Le contrat entourant le composant SingleThreaded est que les composants qui implémentent cette interface ne sont pas autorisés à être accessibles par plusieurs threads en même temps. Chaque thread doit avoir sa propre instance de ce composant. Une autre approche consiste à utiliser un pool de composants au lieu de créer une nouvelle instance à chaque fois que le composant est demandé. Pour utiliser un pool, vous devez implémenter l'interface Poolable d'Avalon Excalibur au lieu de cette interface.
ThreadSafe
Le contrat entourant les composants ThreadSafe est que leurs interfaces et implémentations fonctionneront normalement, quel que soit le nombre de threads accédant au composant en même temps. Bien qu’il s’agisse d’un objectif de conception flexible, parfois, en fonction de la technologie que vous utilisez, il n’est tout simplement pas réalisable. Un composant qui implémente cette interface n'a généralement qu'une seule instance dans le système, et d'autres composants utiliseront cette instance.
Autres
Ces classes et interfaces du package racine d'Avalon Framework incluent la hiérarchie des exceptions et certaines classes utilitaires courantes. Mais il y a une classe qui mérite d’être mentionnée. Version
La technologie de version JavaTM est spécifiée dans le fichier manifeste du package jar. Le problème est que vous perdez les informations de version lorsque le fichier jar est décompressé et que les informations de version sont placées dans un fichier texte facilement modifiable. Lorsque vous combinez ces problèmes avec une courbe d’apprentissage plus abrupte, il est difficile de vérifier les versions des composants et des interfaces. L'équipe de développement d'Avalon a conçu l'objet Version afin que vous puissiez facilement vérifier et comparer les versions. Vous pouvez implémenter l'objet Version dans votre composant et il sera plus facile de tester le composant approprié ou le numéro de version minimum.
Réaliser le rêve
Nous vous montrerons comment utiliser Avalon Framework et Avalon Excalibur pour implémenter votre application de service. Nous allons vous montrer à quel point Avalon est facile à utiliser.
Après avoir terminé l'analyse, vous devez créer les composants et services qui composent votre système. Avalon ne serait pas d'une grande utilité s'il décrivait simplement quelques habitudes de programmation que vous pourriez utiliser. Mais même ainsi, l’application de ces habitudes et modèles de programmation sera utile pour comprendre l’ensemble du système. Avalon Excalibur fournit des composants et des outils utiles que vous pouvez utiliser dans votre propre système pour vous faciliter la vie. Dans le cadre de notre démonstration, nous passons en revue l'ensemble du processus de définition d'un composant et de extraction d'un document d'un référentiel pour l'implémenter. Si vous vous souvenez de notre discussion sur le serveur métier théorique, nous avons identifié ce composant comme un service. Dans les situations réelles, il existe de nombreuses situations dans lesquelles un composant est un service.
Implémenter le composant
Ici, nous définissons comment implémenter notre composant. Nous passerons en revue l'ensemble du processus d'implémentation du composant DocumentRepository mentionné précédemment. La première chose que nous devons déterminer est le domaine sur lequel notre composante se concentre. Ensuite, nous devons comprendre comment créer et gérer nos composants. Choisissez un domaine d'intérêt
Nous avons défini précédemment les rôles et les interfaces pour le composant DocumentRepository et nous sommes prêts à créer l'implémentation. Étant donné que l'interface DocumentRepository ne définit qu'une seule méthode, nous avons la possibilité de créer un composant thread-safe. Il s’agit du type de composant le plus populaire car il ne permet qu’une consommation minimale de ressources. Pour que notre implémentation soit thread-safe, nous devons vraiment réfléchir attentivement à la manière d'implémenter le composant. Puisque tous nos documents sont stockés dans la base de données et que nous souhaitons utiliser un composant Guardian externe, nous devrons accéder à d'autres composants. En tant que développeurs responsables, nous souhaitons enregistrer des informations qui peuvent nous aider à déboguer nos composants et à suivre ce qui se passe en interne. La beauté du framework Avalon est que vous implémentez uniquement les interfaces dont vous avez besoin et pouvez ignorer celles dont vous n'avez pas besoin. C’est l’avantage de la séparation des préoccupations. Lorsque vous trouvez un nouvel aspect à prendre en compte, il vous suffit d'implémenter l'interface appropriée pour ajouter de nouvelles fonctionnalités au composant. Aucune modification n'est requise sur les pièces qui utilisent votre composant. Puisque la sécurité des threads est un objectif de conception, nous savons déjà que nous devons implémenter l'interface ThreadSafe. L'interface DocumentRepository n'a qu'une seule méthode, donc l'utilisation de l'interface de travail de ce composant répond à cette exigence. Et nous savons que le composant ne sera pas utilisé avant d'être complètement initialisé, ni après sa destruction. Afin de finaliser la conception, nous devons implémenter certaines interfaces implicites. Nous voulons que la solution soit suffisamment sécurisée pour que nous puissions savoir explicitement si un composant a été entièrement initialisé. Pour atteindre cet objectif, nous implémenterons les interfaces Initialisable et Jetable. Étant donné que les informations sur l'environnement peuvent changer ou devoir être personnalisées, nous devons laisser DocumentRepository implémenter l'interface Configurable. La méthode fournie par Avalon pour obtenir une instance du composant requis consiste à utiliser un ComponentManager. Nous devons implémenter l'interface Composable pour obtenir les instances de composants de ComponentManager. Étant donné que DocumentRepository accède aux documents de la base de données, nous devons prendre une décision. Voulons-nous utiliser le composant DataSourceComponent Avalon Excalibur ou voulons-nous implémenter nous-mêmes le code de gestion des connexions à la base de données. Dans cet article, nous utiliserons DataSourceComponent. À ce stade, notre squelette de classe ressemble à ceci : classe publique DatabaseDocumentRepositoryextends AbstractLogEnabledimplements DocumentRepository , Configurable, Composable, Initialised, Component, ThreadSafe{ private boolean initialized = false; private boolean dispos = false private ComponentManager manager = null private String dbResource; = null; /*** Constructeur. Tous les composants ont besoin d'un constructeur public sans argument * pour être un composant légal.*/ public DatabaseDocumentRepository() {} /***Configuration. Remarquez que je vérifie si le composant a déjà été configuré ? Ceci est fait pour appliquer la politique consistant à * appeler Configure une seule fois.*/ public final void configure(Configuration conf) throws ConfigurationException { if (initialized || disposed) { throw new IllegalStateException ( "Illégal call"); } if (null == this.dbResource) { this.dbResource = conf.getChild("dbpool").getValue(); getLogger().debug("Utilisation du pool de base de données : " + this.dbResource ); // Remarquez le getLogger() ? Ceci vient de AbstractLogEnabled // que j'étends à presque tous mes composants. } } /*** Composition. Remarquez que je vérifie si le composant a déjà été initialisé ou supprimé ? Ceci est fait pour appliquer * la politique de bonne gestion du cycle de vie.*/ public final void compose(ComponentManager cmanager) throws ComponentException { if (initialized || disposé) { throw new IllegalStateException ("Appel illégal"); } if (null == this.manager) { this.manager = cmanager; } } public final void initialize() throws Exception { if (null == this. manager) { throw new IllegalStateException("Not Composed"); } if (null == this.dbResource) { throw new IllegalStateException("Not Configured"); } if (disposé) { throw new IllegalStateException("Déjà disposé"); .initialized = true; } public final void dispose() { this.disposed = true; this.manager = null; this.dbResource = null; } public final Document getDocument (demandeur principal, int refId) { if (!initialized || disposé) { throw new IllegalStateException("Illegal call"); } // TODO: FILL IN LOGIC }}
Vous pouvez trouver quelques modèles structurels dans le code ci-dessus. Lorsque vous concevez en pensant à la sécurité, vous devez appliquer explicitement chaque contrat dans votre composant. La sécurité est aussi forte que son maillon le plus faible. N'utilisez un composant que lorsque vous êtes sûr qu'il a été entièrement initialisé. Après sa destruction, ne l'utilisez plus jamais. J'ai mis cette logique ici parce que vous le feriez de la même manière lorsque vous écrivez vos propres cours.
Composants d'instanciation et de gestion de composants
Afin que vous compreniez comment fonctionne la relation conteneur/composant, nous aborderons d'abord la manière manuelle de gérer les composants. Nous verrons ensuite comment l'architecture des composants Excalibur d'Avalon vous cache la complexité. Il y aura encore des moments où vous préférerez gérer les composants vous-même. Mais la plupart du temps, Excalibur a la puissance et la flexibilité nécessaires pour répondre à vos besoins. La méthode manuelle
Tous les composants Avalon sont créés quelque part. Le code qui crée le composant est le conteneur du composant. Le conteneur est chargé de gérer le cycle de vie des composants, de la construction à la destruction. Le conteneur peut avoir une méthode « principale » statique qui peut être appelée à partir de la ligne de commande, ou il peut s'agir d'un autre conteneur. Lorsque vous concevez votre conteneur, n'oubliez pas le modèle de contrôle inverse. Les informations et les appels de méthode circuleront uniquement du conteneur vers le composant. Subversion du contrôle
La subversion du contrôle est l'anti-modèle du contrôle inversé. Le contrôle Subversion est obtenu lorsque vous transmettez une référence à un conteneur à un composant. C'est également le cas lorsque vous laissez un composant gérer son propre cycle de vie. Un code qui fonctionne de cette manière doit être considéré comme défectueux. Lorsque vous mélangez les relations conteneur/composant, leurs interactions rendent le système difficile à déboguer et à auditer en matière de sécurité.
Afin de gérer les composants enfants, vous devez conserver des références à ceux-ci tout au long de leur vie. Avant que le conteneur et les autres composants puissent utiliser le sous-composant, ils doivent terminer leur initialisation. Pour notre DocumentRepository, le code pourrait ressembler à ceci : class ContainerComponent implémente Component, Initialised, Disposable{ DocumentRepository docs = new DatabaseDocumentRepository(); GuardianComponent guard = new DocumentGuardianComponent(); Exception { Logger docLogger = new LogKitLogger( Hierarchy.defaultHierarchy() .getLoggerFor( "document" ) ); this.docs.enableLogging( docLogger.childLogger( "repository" ) ); " ) ); DefaultConfiguration pool = new DefaultConfiguration("dbpool"); pool.setValue("main-pool"); DefaultConfiguration conf = new DefaultConfiguration(""); conf.addChild(pool); this. manager.addComponent( DocumentRepository .ROLE, this.docs ); this.manager.addComponent( GuardianComponent.ROLE, this.guard ); this.docs.compose( this.guard.compose( this.manager ); (conf); this.guard.initialize(); this.docs.initialize(); } public void dispose() { this.docs.dispose(); par souci de brièveté, j'ai supprimé la vérification explicite du code ci-dessus. Vous pouvez voir que la création et la gestion manuelle des composants sont un travail détaillé. Si vous oubliez de faire une étape dans le cycle de vie des composants, vous rencontrerez des bugs. Cela nécessite également une connaissance approfondie du composant que vous instanciez. Une autre approche consiste à ajouter quelques méthodes au ContainerComponent ci-dessus pour gérer dynamiquement l'initialisation du composant.
Autonomie automatisée
Les développeurs sont paresseux par nature, ils passent donc leur temps à écrire un ComponentManager spécial qui agit comme un conteneur pour tous les composants du système. De cette façon, ils n’ont pas besoin d’avoir une compréhension approfondie des interfaces de tous les composants du système. Cela peut être une tâche frustrante. Les développeurs d'Avalon ont créé un tel monstre. L'architecture des composants d'Avalon Excalibur comprend un ComponentManager, qui est contrôlé via des fichiers de configuration XML. Il existe un compromis lorsque vous confiez la responsabilité de la gestion des composants au ComponentManager d'Excalibur. Vous abandonnez un contrôle précis sur les composants inclus dans le CompomentManager. Cependant, si votre système est assez volumineux, le contrôle manuel peut s'avérer frustrant. Dans ce cas, pour des raisons de stabilité du système, il est préférable de gérer de manière centralisée tous les composants du système à partir d’un seul endroit. Puisqu'il existe différents niveaux d'intégration avec l'architecture des composants d'Excalibur, nous commencerons par le niveau le plus bas. Excalibur dispose d'un ensemble d'objets ComponentHandler qui servent de conteneurs indépendants pour chaque type de composant. Ils gèrent l’ensemble du cycle de vie de vos composants. Introduisons le concept d'interfaces de style de vie. Une interface de survie décrit comment le système traite un composant. Puisque la façon dont les composants vivent aura un impact sur le fonctionnement du système, nous devons discuter des implications de certains des modes de vie actuels : · org.apache.avalon.framework.thread.SingleThreadedo n'est pas thread-safe ni réutilisable. o Si aucune autre interface de mode survie n'est spécifiée, le système prendra en compte celle-ci. o Chaque fois que le composant est demandé, une toute nouvelle instance sera créée. o La création et l'initialisation de l'instance sont différées jusqu'à ce que le composant soit demandé. · Le composant org.apache.avalon.framework.thread.Threadsafeo est entièrement réentrant et est conforme à tous les principes thread-safe. o Le système crée une instance et l'accès à celle-ci est partagé par tous les composants Composable. o La création et l'initialisation de l'instance sont terminées lorsque le ComponentHandler est créé. · org.apache.avalon.excalibur.pool.Poolableo n'est pas thread-safe, mais est entièrement réutilisable. o Créez un ensemble d'instances et mettez-les dans le pool Lorsque le composant Composable le demande, le système en fournit une disponible. o La création et l'initialisation de l'instance sont terminées lorsque le ComponentHandler est créé. L'interface ComponentHandler est très simple à gérer. Vous initialisez le constructeur via des classes Java, des objets Configuration, des objets ComponentManager, des objets Context et des objets RoleManager. Si vous savez que votre composant n'aura pas besoin de l'un des éléments ci-dessus, vous pouvez télécharger un null à la place. Après cela, lorsque vous avez besoin d'une référence au composant, vous appelez la méthode "get". Lorsque vous avez terminé, vous appelez la méthode « put » pour renvoyer le composant au ComponentHandler. Le code suivant nous permet de mieux comprendre cela. class ContainerComponent implémente le composant, initialisable, jetable{ ComponentHandler docs = null; ComponentHandler guard = null; public void initialize() throws Exception { DefaultConfiguration pool = new DefaultConfiguration("dbpool"); "pool principal"); DefaultConfiguration conf = new DefaultConfiguration(""); conf.addChild(pool); this.docs.configure(conf); this.docs = ComponentHandler.getComponentHandler( DatabaseDocumentRepository.class, conf, this.manager , null, null); this.guard = ComponentHandler.getComponentHandler( DocumentGuardianComponent.class, null, this.manager, null, null); Logger docLogger = new LogKitLogger( Hierarchy.defaultHierarchy() .getLoggerFor( "document" ) ); .docs.enableLogging( docLogger.childLogger( "dépôt" ) ); this.guard.enableLogging( docLogger.childLogger( "sécurité" ) ); this.manager.addComponent(DocumentRepository.ROLE, this.manager); addComponent(GuardianComponent.ROLE, this.guard); this.guard.initialize(); this.docs.initialize(); } public void dispose() { this.docs.dispose(); }}
Ici, il ne nous manque que quelques lignes de code. Nous avons toujours créé l'objet Configuration manuellement, configuré le Logger et avons encore dû initialiser et détruire l'objet ComponentHandler. Ce que nous faisons ici, c'est simplement pour éviter d'être affecté par les changements d'interface. Vous trouverez peut-être avantageux de coder de cette façon. Excalibur va encore plus loin. Les systèmes les plus complexes possèdent des fichiers de configuration. Ils permettent aux administrateurs d'ajuster les informations de configuration critiques. Excalibur peut lire les fichiers de configuration dans les formats suivants et créer des composants système à partir d'eux.
N'est-ce pas incroyable ? Nous avons initialisé plus de deux fois plus de composants avec plus de deux fois plus de code (6 lignes de code au lieu de 13). Ce fichier de configuration a un inconvénient, il a l'air un peu fou, mais il minimise la quantité de code à écrire. De nombreuses activités se déroulent dans les coulisses d'ExcaliburComponentManager. Pour chaque élément « composant » du fichier de configuration, Excalibur crée un ComponentHandler pour chaque entrée de classe et établit une relation correspondante avec le rôle. L'élément "component" et tous ses éléments enfants constituent la configuration du composant. Lorsque le composant est un ExcaliburComponentSelector, Excalibur lira chaque élément « instance de composant » et effectuera le même type d'opération que précédemment, établissant cette fois une relation correspondante avec l'entrée d'indice. Rendre le fichier de configuration plus beau
Nous pouvons utiliser des alias pour changer l'apparence du fichier de configuration. Excalibur utilise un RoleManager pour fournir des alias pour le système de configuration. RoleManager peut être une classe que vous créez spécialement, ou vous pouvez utiliser DefaultRoleManager et transmettre un objet Configuration. Si j'utilise DefaultRoleManager, je masquerai le fichier de configuration des rôles et d'autres parties du système dans un fichier jar. En effet, les profils de personnages ne seront modifiés que par les développeurs. Voici l'interface RoleManager : interface RoleManager{ String getRoleForName( String shorthandName ); String getDefaultClassNameForRole( String role ); String getDefaultClassNameForHint( String suggest, String shorthand );}
Jetons un coup d'œil à la manière dont Excalibur est utilisé dans notre framework. Gestionnaire de rôles. Tout d’abord, Excalibur parcourt tous les éléments enfants de l’élément racine. Cela inclut tous les éléments "composant", mais cette fois Excalibur ne reconnaît pas le nom de l'élément, il demande au RoleManager quel rôle nous allons utiliser pour ce composant. Si RoleManager renvoie null, alors l'élément et tous ses éléments enfants sont ignorés. Ensuite, Excalibur dérive le nom de classe du nom de rôle. L'approche finale consiste à mapper dynamiquement les noms de classe aux sous-types de ComponentSelector. Excalibur fournit une implémentation par défaut de RoleManager, qui utilise un fichier de configuration XML. Les balises sont assez simples et cachent toutes les informations supplémentaires que vous ne souhaitez pas que les administrateurs voient.
Pour utiliser RoleManager, vous besoin de changer la méthode "initialisation" dans la classe conteneur. Vous utiliserez le générateur de configuration pour construire une arborescence de configuration à partir de ce fichier. N'oubliez pas que si vous envisagez d'utiliser un RoleManager, vous devez appeler la méthode "setRoleManager" avant d'appeler la méthode "configure". Pour montrer comment obtenir ce fichier XML à partir du chargeur de classe, je vais montrer la technique ci-dessous : DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();Configuration sysConfig = builder.buildFromFile("./conf/system.xconf");Configuration roleConfig = builder.build( this.getClass().getClassLoader() .getResourceAsStream("/org/apache/bizserver/docs/document.roles"));DefaultRoleManager rôles = new DefaultRoleManager();roles.enableLogging(Hierarchy.getDefaultHierarchy () .getLoggerFor("document.roles"));roles.configure(roleConfig);this.manager.setLogger( Hierarchy.getDefaultHierarchy() .getLoggerFor("document") );this.manager.contextualize( new DefaultContext() ); this.manager.setRoleManager( Roles );this.manager.configure( sysConfig );this.manager.initialize();
Maintenant que nous avons ajouté 6 lignes de code, jetons un coup d'œil aux avantages que cela apporte. Notre fichier de configuration final peut être écrit comme ceci :
Utilisation du composant
Maintenant que nous avons créé nos composants, nous allons les utiliser. Quelle que soit la manière dont le composant est initialisé et géré, la manière dont vous y accédez est la même. Vous devez implémenter l'interface Composable afin d'obtenir une référence du ComponentManager. Le ComponentManager contient des références à tous les composants dont vous avez besoin. Pour faciliter la discussion, nous supposerons que le ComponentManager que nous obtenons est configuré selon le fichier de configuration final de la section précédente. Cela signifie que nous avons un référentiel, un tuteur et deux sources de données. Principes d'utilisation de l'infrastructure de gestion des composants
L'infrastructure de gestion des composants nécessite que vous publiiez les composants auxquels vous obtenez des références. La raison de cette restriction est de gérer correctement les ressources des composants. Le ComponentManager est conçu en tenant compte du fait que vous disposez de différents types de composants pour des rôles spécifiques. Un autre aspect unique de ComponentSelector est qu'il est également conçu comme un composant. Cela nous permet d'obtenir un ComponentSelector à partir d'un ComponentManager. Il existe deux manières légales de gérer les références à des composants externes. Vous pouvez obtenir des références lors de l'initialisation et les libérer lors de leur destruction. Vous pouvez également placer le code qui gère les composants dans des blocs try/catch/finally. Les deux méthodes présentent des avantages et des inconvénients. Approche d'initialisation et d'élimination
class MyClass implémente Component, Composable, Jetable{ ComponentManager manager ; Guardian myGuard ; /*** Obtenez une référence à un garde et conservez la référence au * ComponentManager.*/ public void compose(ComponentManager manager) throws ComponentException { if (this.manager == null) { this.manager = manager; myGuard = (Guardian) this.manager.lookup(Guardian.ROLE); } } /*** C'est la méthode utilisée par le Guardian.*/ public void myMethod() throws SecurityException { this.myGuard.checkPermission(new BasicPermission(" test")); } /*** Débarrassez-vous de nos références*/ public void dispose() { this.manager.release(this.myGuard); this.myGuard = null; this.manager = null; }}
Depuis exemple Comme vous pouvez le voir dans le code, c'est facile à faire. Lorsque l'objet reçoit pour la première fois le ComponentManager, il obtient une référence au composant Guardian. Si vous pouvez vous assurer que le composant Guardian est thread-safe (implémente l'interface ThreadSafe), il vous suffit de faire ces choses. Malheureusement, vous ne pouvez pas garantir cela à long terme. Afin de gérer correctement les ressources, nous devons libérer la référence au composant une fois que nous en avons terminé. C'est pourquoi nous gardons une référence au ComponentManager. Le principal inconvénient de cette approche réside dans le traitement des composants dans un pool de composants. Une référence à un composant maintient le composant en vie. Si l'objet a une courte durée de vie, cela peut ne pas poser de problème ; mais si l'objet est un composant géré par l'architecture de gestion des composants Excalibur, sa durée de vie continuera tant qu'il y aura des références à celui-ci. Cela signifie que nous transformons réellement le pool de composants en une usine de composants. Le principal avantage de cette approche est que le code permettant d’obtenir et de publier le composant est clair. Vous n'avez pas besoin de comprendre le code de gestion des exceptions. Une autre différence subtile est que vous associez l'existence du Gardien à la possibilité d'initialiser cet objet. Une fois qu'une exception est levée lors de la phase d'initialisation d'un objet, vous devez supposer que l'objet n'est pas un objet valide. Parfois, vous souhaitez que le programme échoue si le composant requis n'existe pas, ce n'est pas un problème. Lors de la conception de composants, vous devez vraiment être conscient de cette signification implicite.
Approche de gestion des exceptions
class MyClass implémente Composable, Jetable{ ComponentManager manager /*** Obtenez une référence à un garde et conservez la référence au * ComponentManager.*/ public void compose(ComponentManager manager) throws ComponentException { if (this.manager == null) { this.manager = manager; } } /*** C'est la méthode qui permet d'obtenir le Gardien.*/ public void myMethod() throws SecurityException { Guardian myGuard = null ; try { myGuard = (Guardian) this.manager.lookup(Guardian.ROLE); ; } catch (ComponentException ce) { throw new SecurityException (ce.getMessage()); } catch (SecurityException se) { throw se } enfin { if (myGuard != null) { this.manager.release(myGuard }) } } /*** Effectuer la partie critique du code.*/ public void criticSection(Guardian myGuard) throws SecurityException { myGuard.checkPermission(new BasicPermission("test") }}
Comme vous pouvez le constater, ce code est un peu complexe. Pour le comprendre, vous devez comprendre la gestion des exceptions. Cela ne pose peut-être pas de problème puisque la grande majorité des développeurs Java savent gérer les exceptions. De cette façon, vous n'avez pas à vous soucier trop de la durée de vie du composant, car il est publié dès que nous n'en avons plus besoin. Le principal inconvénient de cette approche est l’ajout d’un code de gestion des exceptions, qui est plus complexe. Afin de minimiser la complexité et de faciliter la maintenance du code, nous extrayons le code de travail et le plaçons dans une autre méthode. N'oubliez pas que dans le bloc try, nous pouvons obtenir autant de références au composant que nous le souhaitons. Le principal avantage de cette approche est que vous pouvez gérer les références de composants plus efficacement. De même, si vous utilisez des composants ThreadSafe, il n'y a pas de réelle différence, mais si vous utilisez des composants du pool de composants, il y a une différence. Il y a une légère surcharge liée à l'obtention d'une nouvelle référence à un composant à chaque fois que vous l'utilisez, mais la probabilité d'être obligé de créer une nouvelle instance de composant est considérablement réduite. Tout comme le fonctionnement de l’initialisation et de la destruction, il existe une différence subtile que vous devez comprendre. La gestion des exceptions est effectuée de manière à ce que le programme n'échoue pas lors de l'initialisation si le gestionnaire ne trouve pas le composant. Comme mentionné précédemment, cela n’est pas totalement dénué de fondement. Souvent, vous vous attendez à ce qu'un certain composant soit présent, mais le programme n'a pas besoin d'échouer si le composant attendu n'est pas présent.
Récupérer un composant à partir d'un ComponentSelector
Pour la plupart des opérations, il vous suffit d'utiliser le ComponentManager. Maintenant que nous avons décidé que nous avons besoin de plusieurs instances de DataSourceComponent, nous devons savoir comment obtenir celle que nous voulons. ComponentSelector est un peu plus compliqué que ComponentManagers car il existe des astuces pour obtenir la référence souhaitée pendant le traitement. Un composant appartient à un rôle spécifique, comme nous l'avons déjà précisé. Cependant, nous devons parfois en choisir un parmi plusieurs composants d’un personnage. ComponentSelector utilise un objet arbitraire comme indice. La plupart du temps, cet objet est une String, même si vous souhaiterez peut-être utiliser un objet Locale pour obtenir un composant correctement internationalisé. Dans le système que nous avons mis en place, nous choisissons d'utiliser une chaîne pour sélectionner la bonne instance de DataSourceComponent. Nous nous sommes même donné un élément Configuration pour spécifier quelle chaîne est nécessaire pour obtenir le bon composant. C'est une bonne pratique à suivre car elle facilite l'administration du système. Cela permet aux administrateurs système de voir plus facilement les références à d'autres composants plutôt que de devoir se souvenir de ces valeurs de configuration magiques. Conceptuellement, il n'y a aucune différence entre obtenir un composant à partir d'un ComponentSelector et obtenir un composant à partir d'un ComponentManager. Il ne vous reste plus qu'un pas. N'oubliez pas que ComponentSelector est également un composant. Lorsque vous recherchez le rôle de ComponentSelect, ComponentManager préparera le composant ComponentSelector et vous le renverra. Ensuite, vous devez sélectionner le composant via celui-ci. Pour illustrer cela, je développerai le code de gestion des exceptions évoqué plus tôt. public void myMethod() throws Exception{ ComponentSelector dbSelector = null; DataSourceComponent datasource = null; try { dbSelector = (ComponentSelector) this.manager.lookup(DataSourceComponent.ROLE + "Selector"); .useDb); this.process(datasource.getConnection()); } catch (Exception e) { throw e; } enfin { if (datasource != null) { dbSelector.release(datasource); ) { this.manager.release(dbSelector); } }}
Vous pouvez voir que nous avons obtenu une référence au ComponentSelector en utilisant le rôle du composant spécifié. Nous avons suivi la convention de dénomination des rôles mentionnée précédemment et ajouté « Sélecteur » comme suffixe au nom du rôle. Vous pouvez utiliser une interface statique pour gérer tous les noms de rôles dans le système afin de réduire le nombre de concaténations de chaînes dans votre code. C'est également parfaitement acceptable de faire cela. Ensuite, nous devons obtenir une référence au composant DataSource à partir du ComponentSelector. Notre exemple de code suppose que nous avons obtenu les informations requises de l'objet Configuration et que nous les avons placées dans une variable de classe nommée "useDb".
Classes d'outils d'Excalibur
La dernière section vous présente plusieurs types de composants et d'outils fournis par Apache Avalon Excalibur. Ces classes d'outils sont robustes et peuvent être utilisées dans des systèmes de production réels. Nous avons un projet hiérarchique informel appelé "Scratchpad" dans lequel nous élaborons les détails d'implémentation de nouvelles classes d'outils potentielles. Les outils de Scratchpad varient en qualité et rien ne garantit qu'ils seront utilisés de la même manière, même si vous pouvez les trouver utiles. Interface de ligne de commande (CLI)
La classe d'outils CLI est utilisée dans certains projets, notamment Avalon Phoenix et Apache Cocoon, pour traiter les paramètres de ligne de commande. Il fournit un mécanisme pour imprimer les informations d'aide et peut gérer les options de paramètres sous la forme de noms courts ou longs.
Classe d'outils de collecte
Les classes d'utilitaires de collection apportent certaines améliorations à l'API JavaTM Collections. Ces améliorations incluent la possibilité de trouver des intersections entre deux listes et une PriorityQueue, qui est une amélioration de Stack qui permet d'implémenter les modifications de priorité des objets dans une simple pile premier entré, dernier sorti.
Gestion des composants
Nous avons évoqué cet aspect de l'utilisation plus tôt. C'est le monstre le plus complexe d'Excalibur, mais il offre de nombreuses fonctionnalités dans quelques classes seulement. En plus des types de gestion simples SingleThreaded ou ThreadSafe, il existe également un type Poolable. Si un composant implémente l'interface Poolable d'Excalibur au lieu de l'interface SingleThreaded, il conservera un pool de composants et réutilisera les instances. La plupart du temps, cela fonctionne bien. Dans le cas où quelques composants ne peuvent pas être réutilisés, l'interface SingleThreaded est utilisée.
LogKit Management
L'équipe de développement d'Avalon a réalisé que de nombreuses personnes avaient besoin d'un mécanisme simple pour créer des hiérarchies complexes de cibles de journaux. Sur la base d'idées similaires à celles de RoleManager, l'équipe a développé LogKitManager, qui peut être utilisé par le système de gestion des composants Excalibur mentionné précédemment. Basé sur l'attribut "logger", il donnera les objets Logger correspondants pour différents composants.
Classes d'outils de thread
Le package concurrent fournit certaines classes pour aider à la programmation multithread : Lock (implémentation de mutex), DjikstraSemaphore, ConditionalEvent et ThreadBarrier
Datasources
Ceci est basé sur javax.sql. Conception de classe .DataSource, mais simplifiée. Il existe deux implémentations de DataSourceComponent : l'une qui utilise explicitement le pool de connexions JDBC et l'autre qui utilise la classe javax.sql.DataSource du serveur d'applications J2EE.
Classe d'utilitaires d'entrée/sortie (IO)
La classe d'utilitaires IO fournit certaines classes FileFilter et des classes d'utilitaires liées aux fichiers et aux IO.
Pool Implementation
Pool implémente un pool qui peut être utilisé dans diverses situations. L'une des implémentations est très rapide, mais ne peut être utilisée que dans un seul thread. Il est bon d'implémenter le mode FlyWeight. Il existe également un DefaultPool, qui ne gère pas le nombre d'objets dans le pool. SoftResourceManagingPool détermine si l'objet dépasse un seuil lorsqu'il est renvoyé. S'il le dépasse, l'objet est « retiré ». Enfin, HardResourceManagingPool lève une exception lorsque vous atteignez le nombre maximum d'objets. Les trois derniers pools sont tous ThreadSafe.
Classe utilitaire de propriété
La classe utilitaire de propriété est utilisée avec l'objet Contexte. Ils vous permettent d'étendre des "variables" dans un objet résoluble. Voici comment cela fonctionne : "${resource}" recherchera une valeur dans le contexte nommée "resource" et remplacera le symbole par cette valeur.
Conclusion
Avalon a résisté à l'épreuve du temps et est prêt à être utilisé. Les preuves fournies dans cette section peuvent vous aider à vous convaincre, ainsi que les autres, qu'il est préférable d'utiliser un cadre établi plutôt que d'en créer un vous-même.
Vous êtes peut-être déjà convaincu, mais vous avez besoin d'aide pour convaincre vos collègues qu'Avalon est le bon choix. Peut-être que tu as aussi besoin de te convaincre. Quoi qu’il en soit, ce chapitre vous aidera à organiser vos pensées et à fournir des preuves convaincantes. Nous devons combattre la peur, l’incertitude et le doute (FUD) du modèle open source. Concernant les preuves de l'efficacité de l'open source, je recommande de lire l'excellent traitement du sujet N400017 par Eric S. Raymond. Quoi que vous pensiez de ses opinions, ses articles sont compilés dans un livre intitulé La Cathédrale et le Bazar. Des informations seront fournies pour promouvoir. acceptation de l’open source en général.
Avalon fonctionne
Notre résultat est qu'Avalon accomplit ce pour quoi il a été conçu à l'origine. Plutôt que d'introduire de nouveaux concepts et idées, Avalon utilise des concepts éprouvés et les standardise. Le dernier concept ayant influencé la conception d'Avalon est le modèle de séparation des préoccupations, proposé vers 1995. Même à cette époque, la séparation des considérations constituait une approche formalisée des techniques d’analyse des systèmes. La base d’utilisateurs d’Avalon se compte par centaines. Certains projets tels qu'Apache Cocoon, Apache JAMES et Jesktop sont construits sur Avalon. Les développeurs de ces projets sont des utilisateurs du Framework Avalon. Parce qu’Avalon compte un très grand nombre d’utilisateurs, il est bien testé. Conçu par les meilleurs
Les auteurs d'Avalon reconnaissent que nous ne sommes pas le seul groupe d'experts en informatique côté serveur. Nous avons utilisé des concepts et des idées issus des recherches d'autres personnes. Nous répondons aux commentaires de nos utilisateurs. Avalon n'a pas seulement été conçu par les cinq développeurs mentionnés ci-dessus, ils ont également apporté les idées de contrôle inverse, de considérations de séparation et de programmation orientée composants et l'ont conçu. L’avantage de l’open source est que les résultats sont une fusion des meilleures idées et du meilleur code. Avalon a traversé une phase de test d'idées et d'en rejeter certaines parce qu'il existait de meilleures solutions. Vous pouvez utiliser les connaissances acquises par l'équipe de développement d'Avalon et les appliquer à vos propres systèmes. Vous pouvez utiliser les composants prédéfinis d'Excalibur dans vos projets, et ils sont testés pour fonctionner sans erreur sous une charge importante.
Licence de compatibilité
La licence logicielle Apache (ASL) est compatible avec toute autre licence connue. Les plus grandes exceptions connues sont la licence publique GNU (GPL) et la licence publique Lesser GNU (LGPL). L'important est que l'ASL est assez conviviale pour le développement collaboratif et ne vous oblige pas à publier le code source si vous ne le souhaitez pas. à. Le serveur HTTP très respecté d'Apache Software Foundation est sous la même licence.
R&D basée sur les clusters
La plupart des utilisateurs d'Avalon contribuent d'une manière ou d'une autre. Cela répartit la charge de travail de développement, de débogage et de documentation sur un certain nombre d'utilisateurs. Cela montre également que le code d'Avalon a fait l'objet d'un examen par les pairs plus approfondi que ce qui est possible avec le développement d'une entreprise. De plus, les utilisateurs d'Avalon prennent en charge Avalon. Bien que les projets open source ne disposent généralement pas d'un service d'assistance ou d'une assistance téléphonique pour l'argent chaud, nous avons une liste de diffusion. Beaucoup de vos questions peuvent recevoir une réponse rapide via la liste de diffusion, plus rapidement que certaines lignes d'assistance téléphonique.
Analyse et conception simplifiées
Le développement basé sur Avalon aide les développeurs à atteindre un état d'esprit. Dans cet état d'esprit, le travail du développeur se concentre sur la manière de découvrir des composants et des services. Maintenant que les détails sur la durée de vie des composants et des services ont été analysés et conçus, les développeurs n'ont plus qu'à choisir ce dont ils ont besoin. Il est important de souligner qu’Avalon n’a pas commencé par remplacer l’analyse et la conception traditionnelles orientées objet, mais par les améliorer. Vous utilisez toujours les mêmes techniques qu'auparavant, mais vous disposez désormais d'un ensemble d'outils pour mettre en œuvre vos conceptions plus rapidement.
Avalon est prêt
Avalon Framework, Avalon Excalibur et Avalon LogKit sont prêts à être utilisés. Ils ont mûri et ne font que s'améliorer. Bien qu'Avalon Phoenix et Avalon Cornerstone soient en cours de développement intensif, le code que vous écrivez sur cette base fonctionnera à l'avenir avec seulement des modifications mineures.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!