Portrait de Philippe PradosPrados.FRProgrammation, architecture & sécurité — depuis 1998

Les paramètres avec Java

Les applications et les frameworks ont besoin de paramètres pour adapter leurs comportements à différents usages, différents déploiements ou différents contextes d’exécution. Ce document explique les différentes stratégies qu’offre Java pour gérer les paramètres, et sélectionne les solutions pérennes les plus souples.

Par Philippe PRADOS - 2003
www.prados.fr

L’informatique est un monde complexe, car il y a toujours de nombreux paramètres à valoriser pour pouvoir utiliser correctement une application. Si l’un d’entre eux n’est pas correct, l’application ne fonctionne pas correctement. Il est alors très difficile de retrouver la cause de l’erreur ou de comprendre le sens caché d’un paramètre. Le développeur explique que son programme fonctionne correctement dans d’autres contextes et qu’il ne comprend pas pourquoi il ne fonctionne pas maintenant. En effet, le même code peut produire des effets différents suivant les paramètres utilisés. Un programme n’est pas simplement un ensemble d’instruction d’un langage de développement. Les paramètres font partie du programme.

Comment améliorer la qualité des programmes ? En réduisant le plus possible les paramètres. Moins il y a de possibilité de modifier le comportement d’un programme, moins il y a de risque d’erreurs. Malheureusement, ce n’est souvent pas possible. En effet, il faut tenir compte de l’environnement d’accueil du programme. Où est-il déployé ? Quels sont les paramètres du système d’exploitation ? Comment sont organisés les différents composants ? Où se trouve la base de données ? De quel type est-elle ? Quel utilisateur choisir pour l’utiliser ? Quel est son mot de passe ?

Un paramètre est une valeur statique arbitraire, ayant un impact sur le comportement d’une application. Il y a plusieurs familles de paramètres suivant les critères de modifications. Un paramètre de framework ne se traite pas comme un paramètre de déploiement. Un paramètre statique est différent d’un paramètre pouvant être mis à jour à chaud.

Pour chaque paramètre, il faut identifier précisément sa philosophie d’usage. Suivant les cas, la bonne approche pour l’implémenter n’est pas la même. Il faut ensuite s’intéresser à ce que représente le paramètre. Nous allons étudier ces différents sujets.

Familles de paramètres

Nous pouvons classer les paramètres en cinq familles, et pour chacune, une sous-famille concernant les paramètres contextuels.

Famille statique

La première catégorie de paramètre est la famille statique. Il s’agit de paramètres influençant la compilation de l’application. Ces paramètres sont très peu volatiles. Ils existent pour fixer arbitrairement le comportement d’un algorithme. Par exemple, une application désire utiliser des conteneurs d’objets. Il est possible d’indiquer une valeur initiale pour la taille du conteneur. Cette valeur dépend de l’application et de l’usage qui est fait du conteneur. Afin de se réserver la possibilité de modifier tardivement cette taille initiale, le développeur peut externaliser cette constante dans une variable figée. S’il désire la modifier, il ne doit pas revoir le code en recherchant les différents endroits où cette valeur est utilisée,

Dans ce cas, la meilleure approche consiste à utiliser une variable static finale.

class MaClasse
{
  private static final
    int
        MonParamètre=100;
  ...
}

Se pose alors la question de la visibilité de ce paramètre. S’il ne doit être utilisé que dans la classe qui le déclare, il doit être privé. Si quelques classes du même package peuvent en avoir besoin, un accès package est acceptable.

Par contre, si ce paramètre peut être utilisé par d’autres package, il faut faire très attention à la rédaction du code. Il faut d’abord se poser la question s’il est vraiment justifié d’utiliser ce paramètre ailleurs que dans la classe qui le déclare. N’y a-t’il pas d’autres solutions pour répondre au besoin ? Créer un autre paramètre avec la même valeur est souvent préférable. Cela limite les effets de bords lors de la modification de sa valeur. Un paramètre statique utilisé par différent package se justifie rarement.

Attention, il ne faut pas confondre un paramètre avec une constante. Un paramètre peut évoluer, même si c’est rare. Une constante n’évolue jamais. Par exemple, les codes d’erreurs du protocole http sont proposés dans des variables statiques de l’interface HttpServletResponse, avec un accès public. Cela se justifie car ces constantes ne peuvent plus évoluer sans remettre en cause Internet.

S’il est vraiment nécessaire de proposer un paramètre statique avec une visibilité publique, il faut initialiser sa valeur en dehors de la déclaration de la variable.

class MaClasse
{
  public static final int MonParamètre;
  static
  {
    MonParamètre=100;
  }
  ...
}

Pourquoi procéder ainsi ? Java est ainsi fait que la valeur d’une variable static final est compilée en dur dans les appelants. Examinons le code suivant :

class MaClasse
{
  public static final int MonParamètre=100;
  ...
}
class UneAutreClasse
{
  public void main(String[] args)
  {
   int z=MaClasse.MonParamètre;
  }
}

Le code produit par le compilateur pour la méthode UneAutreClasse.main() est le suivant.

public void main(String[] args)
{
  int z=100;
}

Que se passe-t’il si la valeur de MonParamètre est modifiée en 150 ? Si la classe UneAutreClasse n’est pas recompilée, l’ancienne valeur, la valeur 100, reste présente dans la méthode main().

class MaClasse
{
  public static final int MonParamètre=150;
  ...
}

class UneAutreClasse
{
  public void main(String[] args)
  {
    int z=100;
  }
}

Il n’est pas rare d’avoir cet effet car les différents composants d’une application java sont présents dans des archives indépendantes. Lors de l’intégration d’une nouvelle version d’une archive, il est rare de recompiler toutes les autres. L’application est généralement recompilée, mais pas tous les frameworks utilisés par l’application.

Pour éviter cela, il faut initialiser le paramètre en deux étapes, comme montré plus haut. Ainsi, le code généré pour la méthode main() est différent.

class UneAutreClasse
{
  public void main(String[] args)
  {
    int z=MaClasse.MonParamètre;
  }
}

Il n’y a plus de risque de non-propagation de la modification du paramètre. Ainsi, il est plus facile d’intégrer des frameworks différents, s’utilisant mutuellement.

Famille framework

La deuxième famille de paramètres concerne les paramètres des frameworks1. Il s’agit de paramètres permettant à l’utilisateur du framework de l’adapter à ses besoins.

Il n’est pas facile d’offrir un paramétrage efficace pour cette catégorie. En effet, il faut tenir compte de plusieurs contraintes. Plusieurs parties de l’application peuvent vouloir utiliser des valeurs différentes pour les paramètres. Certains paramètres doivent pouvoir être enrichis pour différente partie de l’application ou par d’autres frameworks. La localisation des paramètres doit respecter les contraintes de déploiement des applications Java.

Regardons dans un premier temps comment java organise le déploiement des applications. Depuis la première version, un fichier dérivé du format zip est utilisé pour packager les applications. Des conventions d’organisation des fichiers au sein des archives sont proposées afin de permettre d’enrichir ce format, et de décrire les informations de déploiement nécessaires. Il y a plusieurs formats dérivés du zip. Citons les archives Jar pour les applets ou les différentes librairies, les archives WAR pour les applications Web, les archives EAR pour les applications EJB, etc. Chaque format peut posséder un membre d’un autre format. Par exemple, une archive EAR possède une archive WAR qui possède des archives JAR.

Java utilise le chargeur de classe (ClassLoader) pour pouvoir récupérer les différentes informations présentes dans les archives. L’existence d’un choix de déploiement ne doit pas être connue de l’application. C’est le chargeur de classes qui s’occupe de livrer les fichiers lorsque c’est nécessaire. Il est alors parfaitement envisageable de déployer autrement une application web. Par exemple, un chargeur de classes peut interroger une base de données pour obtenir les fichiers .class ou les ressources. Les classes et les ressources peuvent être récupérées au fur et à mesure d’une connexion http ou ftp…

Pour permettre cette indépendance entre l’application et la stratégie de déploiement, la classe ClassLoader offre plusieurs méthodes. Citons : getRessource(), getRessourceAsStream() et getRessources(). La classe Class reprend deux de ces méthodes pour enrichir automatiquement le nom de la ressource du nom du package de la classe.

La récupération des paramètres doit respecter cette philosophie sous risque de violer les normes Java présentes et futures. Par exemple, si une application utilise un fichier de paramétrage et le consulte sans passer par le chargeur de classes, celui-ci peut ne pas être disponible lors du déploiement. Par exemple, les fichiers WAR ne sont pas obligatoirement dé-archivés lors de l’installation d’une application web. Dans ce cas, le fichier de paramètres ne peut pas être récupéré en tant que fichier car il n’existe pas après le déploiement. La ressource existe dans l’archive WAR, mais pas le fichier. C’est pour cela que la méthode getRealPathName() des moteurs de Servlet peut retourner la valeur null.

Le mécanisme de chargement des ressources internationales utilise cette approche. Un chargeur de classes est utilisé pour récupérer les chaînes de caractères localisées. Il est aisé d’ajouter une nouvelle langue à une framework. Il faut pour cela créer un nouveau fichier properties en respectant la convention de nomage. Il n’est pas nécessaire de modifier le framework pour cela. L’ajout d’un fichier dans l’archive de l’application est suffisant. Si le code est bien écrit, il n’y a pas de risque de collision.

Il faut s’interroger sur la localisation des paramètres. S’ils sont spécifiques à un framework, afin d’éviter les collisions de noms avec d’autres frameworks, il faut placer les fichiers dans les packages correspondant au framework. C’est pour cela que la classe Class propose des API spécifiques ajoutant les noms des packages. Par exemple, un framework MonSuperFrameworkAMoi est proposé. Pour respecter les conventions de nommage de Java, le framework doit être présent dans un package dont le nom est dérivé d’un nom de domaine Internet. Disons org.monsuperframeworkamoi. Les fichiers de paramétrages doivent être présents dans ce package. Ainsi, le framework peut charger le fichier comme ceci :

getClass().getRessourceAsStream("param.ini");

Cela a plusieurs avantages :

Ensuite, il faut s’interroger sur les paramètres eux-mêmes. S’il s’agit de couple nom/valeur, un fichier de propriété est suffisant. Celui-ci peut être récupéré par la méthode getRessourceAsStream().

Properties prop=new Properties();
prop.load(getClass().getResourceAsStream(
     "parametre.properties"));

La ressource parametre.properties doit être présente dans le répertoire correspondant au package de la classe.

Attention, il ne faut surtout pas utiliser la classe RessourceBundle pour récupérer les paramètres. Cette classe est capable de récupérer un ensemble de clef/valeur, mais utilise un mécanisme complexe de recherche de la ressource, dépendant de la langue souhaitée. Cela entraîne un travail excessif du programme, sans aucune justification. À moins d’avoir des paramètres différents suivant les langues, il ne faut pas utiliser cette classe pour cela. On ne plante pas un clou avec un tournevis, même si c’est possible.

Parfois, les paramètres sont constitués d’une liste d’informations. Par exemple, le framework JAF2 (Java Activation Framework) possède un fichier de paramétrage indiquant les différents types MIME qu’il sait traiter. (Un type MIME correspond en gros à un format de fichier) Chaque ligne du fichier de paramétrage indique un type MIME et la classe chargée de l’interpréter. Ce fichier est présent dans l’archive de JAF, avec quelques valeurs standards. D’autres types doivent pouvoir être ajoutés. Comment réaliser cela sans devoir modifier l’archive de JAF ? À priori, il faudrait modifier le fichier présent dans l’archive standard de JAF ! La méthode getRessouces() (avec un ‘s’) va aider JAF, car elle est capable de retourner une liste de toutes les ressources de mêmes noms accessibles par un chargeur de classes. L’application peut analyser le premier fichier, puis le suivant et ainsi de suite. Cette approche permet l’enrichissement des paramètres, sans devoir gérer un fichier unique devant faire l’union de tous les paramètres. Elle respecte la philosophie Open/close de la programmation objet : ouvert aux évolutions et fermé aux modifications. Il est possible d’enrichir le framework sans devoir modifier quoi que ce soit. Si une nouvelle version de JAF est proposée, avec de nouveaux paramètres par défaut, il n’est pas nécessaire de modifier le fichier de paramétrage de l’application. Pour enrichir JAF, il faut créer une nouvelle archive possédant les ressources de paramétrages complétant les paramètres initiaux.

Par exemple, pour rechercher tous les fichiers /param.ini accessibles par le chargeur de classe, il faut rédiger un code comme celui-ci.

for (Enumeration e=getClass().getClassLoader()
      .getResources("/param.ini");
      e.hasMoreElements();)
{
  URL url=(URL)e.nextElement();
  InputStream in=url.openStream();
  ...
}

Ou bien, avec la future syntaxe de java :

for (Enumeration<URL>
     e : getClass().getClassLoader()
     .getResources("/param.ini"))
{
  InputStream in=e.openStream();
  ...
}

Dans vos frameworks, tous les paramètres pouvant être enrichis doivent utiliser cette approche. Pensez aux listes de valeurs et également aux syntaxes XML. Plusieurs fichiers XML peuvent s’enrichir mutuellement.

Parfois, il y a dissonance entre les paramètres nécessaires à une fonctionnalité d’une application et les paramètres nécessaires à une autre fonctionnalité. Comment avoir deux valeurs différentes pour les mêmes paramètres ? Comment utiliser différents jeux de paramètres pour le même framework ? Devant récupérer les paramètres à partir des ressources du chargeur de classes l’ayant installé, il est possible d’utiliser plusieurs versions du même framework dans la même application. Utilisez pour cela des chargeurs de classes différents. Chacun propose alors son jeu de paramètres. Cela n’est possible que si le framework respecte la philosophie de java pour le chargement des paramètres, en les récupérant à l’aide du chargeur de classes.

C’est ce qui se passe lorsqu’un serveur de servlet propose plusieurs applications. Chacune peut avoir son jeu de paramètres, indépendamment des autres applications. Cela n’est possible qu’avec l’utilisation d’un chargeur de classe.

Famille JVM

Certains paramètres modifient le comportement de la machine virtuelle. Java propose plusieurs approches pour ces paramètres.

Dans le répertoire d’installation de la JVM, il existe différents fichiers et répertoires permettant d’initialiser certains paramètres pour toutes les machines virtuelles. C’est le cas par exemple du fichier java.policy. Celui-ci permet de décrire les règles globales de sécurité. Il interdit l’utilisation de certaines API pouvant être dangereuses.

En général, ces paramètres peuvent être modifiés pour une instance particulière de la JVM. Il faut alors ajouter des informations complémentaires dans la ligne de commande. Le paramètre -D de la ligne de commande permet de valoriser ou de modifier des paramètres globaux. Cela permet, par exemple, d’indiquer une liste de drivers JDBC différentes ou d’utiliser un nouveau code page.

java  Djdbc.drivers=COM.ibm.db2.jdbc.app.DB2Driver|
        org.hsql.jdbcDriver …

La classe System permet de consulter, et parfois de modifier, les paramètres globaux d’une JVM.

System.getProperty("java.vm.version")

Famille Déploiement

Les paramètres de déploiement ne peuvent pas être présents dans les archives de déploiement, car ils dépendent de la localisation physique de l’application et de ces composants périphériques. Les choix de répartition des composants, des bases de données, des bases LDAP ou autres systèmes d’informations ne doivent généralement pas être figés dans les paramètres de déploiement. Ainsi, il est plus facile de déployer la même application en test, pré-production ou production. Il suffit de modifier la valeur de ces quelques paramètres lors de l’installation.

Ils faut réduire le nombre de ces paramètres, car ils sont une grande source d’erreurs, complexes à interpréter. Moins il y a de paramètres, moins il y a de risque.

Pour pouvoir tenir les charges importantes de certains sites Internet, la même application peut être déployée sur différents serveurs pour former un groupe. Certains paramètres seront particuliers à chaque serveur, d’autres devront être partagés entre les membres du groupe.

Pour externaliser les paramètres de déploiement, J2EE propose d’utiliser un référentiel externe, accessible via l’API JNDI. Un serveur JNDI est un référentiel permettant d’enregistrer des objets Java, des valeurs de paramètres, des liens vers d’autres objets, etc. Il existe plusieurs types de référentiel compatible avec l’API JNDI. Citons les serveurs de nom Corba, les serveurs Ldap ou d’autres annuaires.

Le référentiel possède les informations et les objets paramétrés pour les déploiements. Un utilitaire externe permet de valoriser les paramètres présents dans les serveurs JNDI.

Comment savoir où se trouve le serveur JNDI ? Il faut un premier paramètre pour chaque serveur ! Pour accéder à ce référentiel, la ressource jndi.properties est valorisée par le serveur d’application ou la machine virtuelle. Ainsi, il est possible de retrouver le serveur JNDI et d’y piocher les paramètres nécessaires à l’application.

Chaque paramètre est identifié par une clef unique arborescente, dépendant du type de serveur JNDI utilisé. Le format de la clef est différent pour un serveur LDAP ou un serveur CORBA. Sur un serveur LDAP, il faut utiliser une clef du type « cn=DataSource,cn=jdbc,dc=mon site,dc=org ». Pour une clef CORBA, elle est du type « /jdbc/DataSource ». Cela entraîne que la clef référençant un paramètre doit elle-même être paramétré pour accepter des serveurs JDNI différents ! Le nom d’un paramètre est un paramètre ! Le serpent se mort la queue.

Pour régler cette difficulté, une approche consiste à utiliser un pseudo serveur JNDI intermédiaire s’occupant de référencer les autres serveurs JNDI. Les serveurs d’applications acceptent des clefs commençant par java:/comp. Cela permet à l’application de s’affranchir du driver JNDI utilisé. L’application n’utilise que des clefs au format java:/comp, le serveur d’application permet de décrire la correspondance entre ces clefs logiques et les clefs réelles, adaptées au serveur JNDI utilisé.

Pour différencier les paramètres dépendant d’un serveur particulier ou d’un groupe, il suffit de valoriser les relations entre les clefs java:/comp et les serveurs JNDI correspondant. Par exemple, un serveur JNDI sera utilisé pour les paramètres du groupe, d’autres serveurs JNDI seront spécifiques à chaque serveur. Une autre approche consiste à regrouper tous les paramètres dans un seul serveur JNDI et d’organiser les paramètres dans des branches différentes, une par serveur physique.

Il ne faut pas s’inquiéter des impacts de performance due à l’interrogation d’un serveur JNDI. En effet, les paramètres ne sont lu qu’une seule fois, au démarrage de l’application.

Famille applicatif

Une autre famille concerne les paramètres spécifiques à une application. Il ne s’agit plus de paramètre d’un framework — d’un composant pouvant être utilisé par plusieurs applications — mais de paramètres fortement liés à une application particulière. Attention, il ne faut pas confondre les paramètres applicatifs des paramètres de déploiement.

Avant de sélectionner un paramètre dans cette catégorie, il faut se demander s’il ne peut pas être considéré comme un paramètre de framework. En effet, les paramètres de framework sont beaucoup plus souples.

Imaginons une application ayant plusieurs modes d’exécution pour l’authentification. Dans un cas, l’application utilise une authentification forte avec certificat, dans l’autre cas, pour simplifier le développement, l’application utilise un simple formulaire.

Le paramètre permettant de sélectionner le mode d’exécution peut être un paramètre applicatif. S’il n’existe pas de framework spécifique d’authentification, il ne s’agit pas d’un paramètre de framework.

Comme exemple de paramétrage applicatif, on peut citer le fichier web.xml qui déclare les différents servlets et d’autres paramètres de sécurité d’une application web. Les paramètres spécifiques au moteur de servlet doivent être localisés dans des marqueurs <context-param/>.

<web-app>
  <context-param>
    <param-name>MonParamètre</param-name>
    <param-value>Ma valeur</param-value>
  </context-param>
  ...
</web-app>

Les fichiers TLD permettent de décrire les librairies de marqueurs pour les pages JSP. Cela permet d’offrir de nouveaux marqueurs pour faciliter la création de page HTML. Dans les spécifications 2.2 des servlets, il fallait indiquer les différentes librairies TLD dans le fichier web.xml. Il s’agissait alors de paramètres applicatifs. Les spécifications 2.3 permettent d’éviter cette déclaration. La présence de fichiers TLD dans le répertoire META-INF des archives permet une déclaration implicite. En ajoutant une archive dans une application WEB, les librairies de marqueurs associées sont directement disponibles. Ce paramétrage est passé de la famille « applicatif » à la famille « framework ».

D’autres paramètres applicatifs peuvent être décrit dans des marqueurs du descripteur de déploiement.

<env-entry>
  <env-entry-name>MonParam</env-entry-name>
  <env-entry-type>java.lang.Integer</env-entry-type>
  <env-entry-value>15</env-entry-value>
</env-entry>

Il faut se méfier des paramètres applicatifs de déverminage. S’ils peuvent être modifiés par un pirate ou s’ils sont laissés par erreur lors du déploiement, cela peut donner accès à beaucoup d’informations.

Sous-Famille contextuelle

Certains paramètres ne sont pas globaux, mais dépendent d’un contexte. Par exemple, il est possible d’associer des paramètres à une servlet particulière. Cela permet d’utiliser la même servlet sous des noms différents, avec des paramètres locaux adaptant le comportement. D’autres paramètres dépendent de la langue de l’utilisateur ou du choix d’une base de données.

La difficulté dans ces situations est d’identifier le contexte lors de la valorisation des paramètres. Différentes approches sont possibles. Parfois, des fichiers XML permettent d’indiquer les différents contextes et les valeurs associés. C’est le cas des paramètres associés aux servlets. Ils sont indiqués lors de la déclaration du contexte.

<servlet>
  <servlet-name>
    UneServlet
  </servlet-name>
  <servlet-class>
    org.java.MaServlet
  </servlet-class>
  <init-param>
    <param-name>monParametre</param-name>
    <param-value>maValeur</param-value>
  </init-param>
</servlet>
<servlet>
  <servlet-name>
    UnAutreServlet
  </servlet-name>
  <servlet-class>
    org.java.MaServlet
  </servlet-class>
  <init-param>
    <param-name>monParametre</param-name>
    <param-value>uneAutre</param-value>
  </init-param>
</servlet>

Dans cet exemple, le même servlet est utilisé dans deux contextes différents. Chacun propose des valeurs différentes pour monParametre.

Les paramètres associés à une langue sont localisés dans des classes ou des fichiers en respectant une convention de nommage permettant d’associer un jeu de paramètre à une langue (voir RessourceBundle). C’est une autre approche pour gérer un contexte de paramètre.

Les différentes familles de paramètres sont exploitables pour valoriser des paramètres contextuels : statique, framework, JVM, déploiement ou applicatif. Il faut respecter chaque famille et l’enrichir d’un contexte.

Synthèse

Voici un tableau reprenant un exemple de paramètre de chaque famille.

FamilleExemple
StatiqueCharacter.LINE_SEPARATOR
FrameworkLa ressource mimetype.default du framework JAF.
JVMLe fichier java.policy du JDK.Les paramètres de la ligne de commande.
DéploiementLes ressources JNDI.
ApplicatifLe fichier web.xml des spécifications J2EE.
ContextuelleLes paramètres des servlets, présent dans le fichier web.xml.Les ressources associées aux différentes langues.

Famille

Exemple

Statique

Character.LINE_SEPARATOR

Framework

La ressource mimetype.default du framework JAF.

JVM

Le fichier java.policy du JDK.

Les paramètres de la ligne de commande.

Déploiement

Les ressources JNDI.

Applicatif

Le fichier web.xml des spécifications J2EE.

Contextuelle

Les paramètres des servlets, présent dans le fichier web.xml.

Les ressources associées aux différentes langues.

Qu’est-ce qu’un paramètre ?

Maintenant que nous avons identifié les différentes familles de paramètres, réfléchissons à ce qu’est réellement un paramètre.

Un paramètre est une information arbitraire (une valeur ou un objet dans un état donné) devant être interprété par une application afin de modifier son comportement.

Comment est-il utilisé ? Un paramètre peut être assimilé à une cellule d’un tableur ou l’utilisateur y place une donnée statique arbitraire. De cette cellule, d’autres cellules peuvent en tirer des informations. Les cellules calculées déduisent différentes informations de synthèses à partir des données initiales. Les cellules calculées peuvent elle-même devenir des paramètres, mais il s’agit alors de paramètres dérivés. Le problème est la gestion des effets de bords de la modification d’un paramètre. Quels sont les impacts de la modification d’un paramètre ? Dans un tableur, il est possible de demander l’affichage des cellules dérivées d’une autre. En procédant par étape, il est alors possible de connaître les cellules dérivées des cellules dérivées et ainsi de suite. Dans un programme informatique, ce chemin de dépendance n’est pas connu. Souvent, les développeurs l’ignorent eux-mêmes. En effet, un paramètre construit pour un besoin donné peut, par la suite, être recyclé pour un autre besoin. Ainsi, en modifiant ce paramètre, l’effet est plus important que prévu. C’est parfois un avantage, car cela évite de valoriser deux paramètres avec les mêmes valeurs. C’est parfois un inconvénient, car il n’est plus possible d’indiquer deux valeurs différentes pour deux usages différents dans la même application.

Les paramètres sont transformés par l’application car le formalisme de description du paramètre ne correspond pas directement au besoin de celle-ci. Par exemple, une information textuelle associée à une clef, présente dans un fichier de paramètre, peut être mémorisée dans un entier présent dans une classe. Une application ne va pas lire systématiquement un fichier pour connaître la valeur d’une variable. Les paramètres sont agrégés, complétés avec d’autres informations, afin d’être immédiatement exploitables dans des objets du langage.

Les types d’informations

Plusieurs types d’informations sont généralement présents dans les paramètres : des informations numériques ou textuelles, des noms de fichiers, des objets sérialisés ou des structures complexes.

Un paramètre peut posséder un lien vers un autre conteneur de paramètre. Par exemple, un paramètre peut indiquer où chercher des paramètres complémentaires. Il ne faut jamais indiquer de nom de fichier. Cela limite fortement les possibilités de déploiement et est une source d’erreurs importante.

Il est préférable d’utiliser une URL, car elle permet d’exprimer un lien vers une autre ressource, en gardant la possibilité d’exploiter différents protocoles pour l’obtenir. Par exemple, un paramètre souhaité indique où se trouve la liste des utilisateurs. En utilisant une URL pour indiquer cette information, il est possible d’indiquer un nom de fichier (format file:), un lien HTTP, un lien FTP ou une ressource d’un chargeur de classes. Ainsi, si l’application évolue vers une architecture en grappe, le fichier des utilisateurs peut être centralisé sur un des serveurs. L’accès à cette information peut s’effectuer par un répertoire partagé. Pour des raisons de sécurité, cela n’est pas conseillé. Il est alors possible de donner accès à ce fichier via un serveur ftp, http ou en utilisant d’autres protocoles.

Les objets sérialisés doivent être construits à l’aide d’une interface spécifique. Les serveurs d’applications offrent généralement le nécessaire pour initialiser une DataSource. Une source de données est une instance sérialisée. Elle peut être placée dans un fichier, une ressource, un serveur JNDI, LDAP, Corba, etc. Suivant la famille du paramètre, la localisation de l’instance sérialisée sera différente. Cela peut être une ressource accessible par un chargeur de classes pour le paramétrage d’un framework, ou dans un référentiel JNDI pour un paramètre de déploiement.

Les structures complexes sont généralement traduites en une grappe d’objets java. Il est envisageable d’offrir une interface utilisateur pour permettre leurs constructions. Souvent, pour permettre à un humain de maîtriser ces structures, et lui permettre de la modifier avec un simple fichier texte, le format XML est utilisé.

Que doit-on paramétrer ?

Il y a des paramètres inévitables : les paramètres de déploiements. En effet, le programme doit être installé quelque part pour fonctionner. Ces paramètres vont décrire à l’application son contexte d’exécution.

Les paramètres de framework doivent être choisis avec circonspection. Trop souvent, le framework cherche à tout faire dans tous les sens, et propose de nombreux paramètres pour cela. Cela complique énormément l’application et à un coup en performance. Il est préférable d’avoir une approche plus objet. Le framework propose un cadre initial simple, et différentes extensions s'occupent d’ajouter les fonctionnalités nouvelles. Les langages objets sont conçus pour faciliter cela. Ainsi, le paramétrage consiste simplement à installer les différentes extensions. J’ai rédigé un framework équivalent à Struts3. Il n’y a que trois paramètres et de nombreuses extensions, à comparer aux dizaines de paramètres de Struts.

Les paramètres applicatifs doivent être peu nombreux. Généralement, ils permettent de placer l’application dans un mode particulier facilitant le déverminage. Sinon, il s’agit généralement de paramètres de déploiement.

Les paramètres contextuels sont souvent présents, car ils permettent d’adapter un composant à plusieurs besoins simultanés pour l’application.

Un paramètre peut-il appartenir simultanément à plusieurs familles ?

Parfois, il est pertinent de permettre la modification d’un paramètre par plusieurs chemins. Un paramètre de framework peut être modifié par un paramètre de déploiement. Il faut respecter l’ordre des familles : statique, framework, JVM, déploiement et applicatif. Un paramètre d’une famille peut être modifié par la suivante, mais pas l’inverse. Par exemple, il n’est pas correct de modifier un paramètre de déploiement par un paramètre de framework.

Peux-on réutiliser le paramètre d’un autre composant ?

Il est très tentant de réutiliser un paramètre d’un autre composant. Il faut faire très attention pour cela. S’il s’agit d’interroger le composant pour lui demander la valeur du paramètre, il n’y a pas de problème. Si, par contre, il s’agit de réutiliser le fichier de paramétrage du composant pour l’interpréter à nouveau et y extraire l’information voulue, cela présente des gros inconvénients. En effet, un composant doit pouvoir évoluer sans impacter les applications l’utilisant. Si un composant décide de se séparer d’un paramètre, ou de lui donner un sens légèrement différent, l’application risque de ne plus fonctionner.

Suivant la famille du paramètre à recycler, il y a plusieurs approches possibles. Si le paramètre à recycler appartient à la famille statique, il n’y a pas de problème car l’accès n’est possible que par la classe. Attention, il faut que ce paramètre soit correctement initialisé, comme il est décrit dans le paragraphe sur la famille statique. Si le paramètre à recycler appartient à la famille framework, il ne faut pas accéder directement au fichier de paramétrage. Soit le framework offre une méthode permettant d’obtenir la valeur du paramètre, soit il faut dupliquer l’information dans un autre paramètre de déploiement ou applicatif. Si le paramètre à recycler appartient à la famille déploiement, il n’y a pas de problème, à la condition que cela ne soit pas pour les besoins d’un framework. De même, si le paramètre à recycler appartient à la famille applicatif, il n’y a pas de problème.

Comment utiliser un paramètre de déploiement dans un framework ?

Comme nous l’avons décrit, les paramètres des frameworks ne doivent pas être des paramètres de déploiement. En effet, ils sont valorisés par l’intégrateur et non par l’installateur. Il ne faut pas devoir reconstruire l’archive de déploiement lors de la modification d’un paramètre. Les paramètres de déploiement pouvant être proposés suivant différentes technologies, comment permettre leurs valorisations sans imposer une technologie plutôt qu’une autre ? La solution réside dans la proposition d’une API permettant de valoriser les paramètres du framework. Par exemple, une API permet d’indiquer à un framework la clef JNDI à utiliser pour accéder à la source de donnée. Il est préférable de devoir indiquer ce paramètre dans un fichier du framework.

Peux-on modifier d’un coup tous les paramètres d’une application ?

Parfois, la valorisation des paramètres d’une application a nécessité beaucoup d’effort. Dans un autre cadre d’exécution, il faut les revoir tous. Par exemple, les paramètres en phase de développement sont différents des paramètres en phase de pré-intégration. Il est souhaitable de pouvoir sélectionner différents jeux de paramètres, sans devoir intervenir individuellement sur chacun.

Suivant la famille des paramètres, il est plus ou moins facile de faire cela. Les paramètres de la famille statique ne permettent pas cette fonctionnalité car il faut intervenir directement sur les classes et les recompiler. Il n’est pas raisonnable de maintenir plusieurs versions de la même classe, chacune étant différente des autres par la valeur des paramètres statiques. Si le besoin est vraiment nécessaire, il peut être judicieux de changer la famille du paramètre pour le faire migrer vers la famille framework. Ainsi, la volatilité du paramètre est étendue, et il est possible d’utiliser l’approche décrite ci-dessous.

Les paramètres de la famille framework, s’ils utilisent l’approche chargeur de classes, peuvent facilement être modifiés en bloc. En suffit pour cela de modifier le CLASSPATH de l’application (CLASSPATH est un paramètre de JVM ou de déploiement). Il faut organiser un répertoire avec tous les paramètres nécessaires pour toute l’application. Ensuite, ajoutez ce répertoire au début du CLASSPATH. Le chargeur de classes s’occupera d’utiliser le jeu de paramètre. Chaque jeu de paramètre est présent dans un répertoire différent.

Les paramètres de la famille JVM sont plus difficiles à traiter. S’il s’agit des fichiers présents dans l’installation de la JVM, il est parfois nécessaire d’installer plusieurs JVM. Chacune ayant son jeu de paramètre. En général, un paramètre de la ligne de commande permet d’indiquer à la JVM d’utiliser un fichier différent du fichier par défaut. Il faut alors modifier le lancement de la JVM pour correspondre au jeu de paramètre voulu. C’est le cas, par exemple, lorsque les critères de sécurité sont allégés en développement par rapport à l’application en intégration. Il faut utiliser plusieurs versions du fichier java.policy.

Les paramètres de la famille « déploiement » sont enregistrés dans des fichiers accessibles par le chargeur de classes ou dans une base JNDI. Pour les paramètres récupérés par le chargeur de classes, l’approche utilisée pour la famille framework est applicable. Pour les paramètres JNDI, il faut lancer plusieurs serveurs JNDI différents, chacun ayant un jeu de paramètres différents. En valorisant différemment le paramètre java.naming.provider.url lors du lancement de la JVM, il est possible de sélectionner le serveur JNDI à utiliser et ainsi, modifier le comportement de l’application.

Les paramètres sont-ils consultable à chaud ?

Il n’y a aucun danger à consulter les paramètres pendant l’exécution de l’application. Une synthèse de tous les paramètres peut être générée lors de l’exécution. Cela peut faciliter le déverminage.

Le framework JMXi de Java (Java Management Extensions) permet d’interroger une application pendant son exécution. Il est envisageable d’offrir un MBean paramétré avec une liste de ressources de paramètre. Un MBean est un bean manipulable par une interface compatible JMX. Le MBean peut alors consulter tous les paramètres et en présenter une synthèse à l’utilisateur. Ce MBean doit posséder la liste des paramètres à récupérer et les techniques pour y accéder. Il faut pouvoir récupérer :

Par exemple, une liste de paramétrage pour le MBean peut avoir la forme suivante :

static:org.monprojet.MaClass.MonParametre
ressource:org/monprojet/MesParametres.cfg
jndi:java:comp/env/MonParametre
jndi:ldap://localhost:389/o=home/
url:http://prod/user.txt

La console JMX peut alors afficher les valeurs des différents paramètres.

Les paramètres sont-ils modifiables à chaud ?

La très grande majorité des paramètres sont initialisés avant le lancement de l’application, et n’évolues plus pendant son exécution. Parfois, il est souhaitable de pouvoir les modifier dynamiquement. Il y a alors plusieurs difficultés à traiter.

Quel est l’impact de la modification d’un paramètre ? Comment maintenir ce lien ? Comment prévenir de l’évolution d’un paramètre ? Que se passe-il pour les traitements en cours d’exécution s’appuyant sur l’ancienne valeur du paramètre ?

Parfois, on rencontre une approche étonnante. Un getteur s’occupe de consulter systématiquement le fichier de paramétrage pour retourner sa valeur. Ainsi, l’information est toujours à jour. Cela présente un risque important. En effet, la modification du paramètre n’est pas transactionnelle. Si l’application interroge à deux instants différents le paramètre, elle peut avoir des valeurs différentes. Si l’algorithme en cours d’exécution n’est pas prévu pour gérer cette situation, l’application peut planter. Cela correspond à un « dirty read » des bases de données relationnelles. En plus, cette approche est pénalisante en termes de performance.

Si le code le permet, il est possible de garder les informations de paramétrages volatiles en cache pendant une période donnée. Lors de la première lecture du paramètre, celui-ci est recherché dans un fichier ou une autre ressource. Il reste en mémoire pendant 10 minutes. C’est seulement lors d’une nouvelle lecture après cette période que le fichier est consulté de nouveau.

Comme on le constate, il est difficile de gérer la modification à chaud d’un paramètre car cela présente un risque important de plantage de l’application.

Si cela est nécessaire, il faut mettre en place un mécanisme de notification. Un bean java représente un paramètre ou plusieurs paramètres. Lors de la modification d’une valeur, un événement est envoyé à toutes les instances intéressées. C’est à elles de s’enregistrer auprès du paramètre pour être prévenu des évolutions. Les informations déduites du paramètre peuvent elle-même être utilisées par d’autres parties de l’application. Il faut alors propager les évènements. Il faut mettre en place un mécanisme similaire à un tableur afin de propager la modification partout où c’est nécessaire.

Ensuite, il faut offrir une technologie permettant de mettre à jour les paramètres. Plusieurs approches sont possibles. Le framework JMX de Java permet de modifier un paramètre pendant l’exécution de l’application. Une interface normalisée permet de contrôler tous les paramètres de l’application.

Il faut alors se poser une question importante : est-ce que la modification doit être propagée dans le fichier de paramétrage pour être pris en compte lors du prochain démarrage de l’application ? Suivant la famille des paramètres cela est plus ou moins facile. Les paramètres de frameworks étant accédés par le chargeur de classes, il n’est pas possible de les modifier car les ressources ne sont pas accessibles en écriture. Les paramètres de déploiements ou applicatifs, présents dans un serveur JNDI peuvent être modifiés. Attention, les serveurs JNDI ne savent pas propager d’évènement lors de la modification d’une information. Il faut alors mettre en place une technologie de communication entre les différents serveurs d’un groupe pour signaler à tous de l’évolution d’un paramètre.

Conclusion

Nous avons vu que la prise en compte des paramètres est plus complexe que ce que nous l'imaginons habituellement. En ayant clairement à l’esprit les classifications décrites ici, les meilleures solutions pour implémenter un paramètre deviennent évidentes. Malheureusement, les frameworks n’utilisent pas toujours ces approches. Cela entraîne plusieurs difficultés :

L’approche eXtreme Programmingii préconise de n’offrir des options ou des paramètres que si on les utilise réellement. Il ne sert à rien de prévoir un découpage de l’application sur 3 grappes de serveurs, avec répartition de charge, tolérances aux pannes, internationalisations complètes, indépendance avec tous les composants (base de donnée, serveur JDNI, serveur LDAP), si l’application ne doit être utilisée que par 20 personnes en Intranet. Il ne faut pas hésiter à remplacer des paramètres par des constantes codées en dur dans le programme.

J’espère vous avoir démontré qu’il y a une solution à tous les problèmes. À condition de réfléchir un moment avant de se précipiter vers une approche qui fonctionne, mais dont on ne mesure pas toujours les impacts négatifs. Les projets gagneraient à imposer une démarche claire dans la classification des paramètres. Gardez toujours à l’esprit que moins il y a de paramètres, moins l’application risque de planter et plus elle est facile à administrer.

1 Un framework est un composant logiciel réutilisable.

2 Ce framework est généralement associé à Javamail. Il permet de coder et de décoder les différents types de documents présents dans un e-mail ou dans tous types de ressources. Le framework s’occupe d’enregistrer des associations entres un type MIME et une classe implémentant une interface standardisée, s’occupant d’interpréter le format, en lecture et en écriture.

3 Struts est une framework open source s’occupant de la couche présentation d’un site web.

i http://java.sun.com/products/JavaManagement/

ii http://www.extremeprogramming.org/