Liste de  cours java

Java et son API support de cours avance [Eng]


Télécharger



Java et son API support de cours avancé [Eng]

Pourquoi l'API JDBC paraît si complexe

Naviguer dans la documentation en ligne de JDBC semble décourageant. En particulier, dans l'interface DatabaseMetaData qui est vraiment énorme, à l'inverse de la plupart des interfaces rencontrées jusque là en Java, on trouve des méthodes telles que



dataDefinitionCausesTransactionCommit( ), getMaxColumnNameLength( ), getMaxStatementLength( ), storesMixedCaseQuotedIdentifiers( ), supportsANSI92IntermediateSQL ( ), supportsLimitedOuterJoins( ), et ainsi de suite. Qu'en est-il de tout cela ?

Ainsi qu'on l'a dit précédemment, les bases de données semblent être depuis leur création dans un constant état de bouleversement, principalement à cause de la très grande demande en applications de base de données, et donc en outils de base de données. Ce n'est que récemment qu'on a vu une certaine convergence vers le langage commun SQL (et il existe beaucoup d'autres langages d'utilisation courante de base de données). Mais même le « standard » SQL possède tellement de variantes que JDBC doit fournir la grande interface DatabaseMetaData afin que le code puisse utiliser les possibilités de la base SQL « standard » particulière avec laquelle on est connecté. Bref, il est possible d'écrire du code SQL simple et portable, mais si on veut optimiser la vitesse le code va se multiplier terriblement au fur et à mesure qu'on découvre les possibilités d'une base de données d'un vendeur particulier.

Bien entendu, ce n'est pas la faute de Java. Ce dernier tente seulement de nous aider à compenser les disparités entre les divers produits de base de données . Mais gardons à l'esprit que la vie est plus facile si l'on peut aussi bien écrire des requêtes génériques sans trop se soucier des performances, ou bien, si l'on veut améliorer les performances, connaître la plate-forme pour laquelle on écrit afin de ne pas avoir à traîner trop de code générique.

Un exemple plus sophistiqué

Un exemple plus intéressant [73] est celui d'une base de données multi-tables résidant sur un serveur. Ici, la base sert de dépôt pour les activités d'une communauté et doit permettre aux gens de s'inscrire pour réaliser ces actions, c'est pourquoi on l'appelle une base de données de communauté d'intérêts, Community Interests Database (CID). Cet exemple fournira seulement une vue générale de la base et de son implémentation, il n'est en aucun cas un tutoriel complet à propos du développement des bases de données. De nombreux livres, séminaires, et packages de programmes existent pour vous aider dans la conception et le développement des bases de données.

De plus, cet exemple implique l'installation préalable d'une base SQL sur un serveur (bien qu'elle puisse aussi bien tourner sur la machine locale), ainsi que la recherche d'un driver JDBC approprié pour cette base. Il existe plusieurs bases SQL libres, et certaines sont installées automatiquement avec diverses distributions de Linux. Il est de votre responsabilité de faire le choix de la base de données et de localiser son driver JDBC ; cet exemple-ci est basé sur une base SQL nommée « Cloudscape ».

Afin de simplifier les modifications d'information de connexion, le driver de la base, son URL, le nom d'utilisateur et son mot de passe sont placés dans une classe séparée :

//: c15:jdbc:CIDConnect.java

// information de connexion à la base de données pour // la «base de données de communauté d'intérêt» (CID). public class CIDConnect {

// Toutes les informations spécifiques à CloudScape: public static String dbDriver = "COM.cloudscape.core.JDBCDriver";

public static String dbURL

="jdbc:cloudscape:d:/docs/_work/JSapienDB";

 public static String user = ""; public static String password = ""; } ///:~

Dans cet exemple, il n'y a pas de protection de la base par mot de passe, le nom d'utilisateur et le mot de passe sont des chaînes vides.

La base de données comprend un ensemble de tables dont voici la structure : border="0" alt="Image">

« Members » contient les informations sur les membres de la communauté,

« Events » et « Locations » des informations à propos des activités et où on peut les trouver, et « Evtmems » connecte les événements et les membres qui veulent suivre ces événements. On peut constater qu'à une donnée « membre » d'une table correspond une clef dans une autre table.

La classe suivante contient les chaînes SQL qui vont créer les tables de cette base (référez-vous à un guide SQL pour une explication du code SQL) :

//: c15:jdbc:CIDSQL.java

// chaînes SQL créant les tables pour la CID.

public class CIDSQL {

public static String[ « sql = {

// Créer la table MEMBERS:

"drop table MEMBERS",

"create table MEMBERS " +

"(MEM_ID INTEGER primary key, " +

"MEM_UNAME VARCHAR(12) not null unique, "+

"MEM_LNAME VARCHAR(40), " +

"MEM_FNAME VARCHAR(20), " +

"ADDRESS VARCHAR(40), " +

"CITY VARCHAR(20), " +

"STATE CHAR(4), " +

"ZIP CHAR(5), " +

"PHONE CHAR(12), " +

"EMAIL VARCHAR(30))",

"create unique index " +

"LNAME_IDX on MEMBERS(MEM_LNAME)",

// Créer la table EVENTS:

"drop table EVENTS",

"create table EVENTS " +

"(EVT_ID INTEGER primary key, " +

"EVT_TITLE VARCHAR(30) not null, " +

"EVT_TYPE VARCHAR(20), " +

"LOC_ID INTEGER, " +

"PRICE DECIMAL, " +

"DATETIME TIMESTAMP)",

"create unique index " +

"TITLE_IDX on EVENTS(EVT_TITLE)",

// Créer la table EVTMEMS:

"drop table EVTMEMS",

"create table EVTMEMS " +

"(MEM_ID INTEGER not null, " +

"EVT_ID INTEGER not null, " +

"MEM_ORD INTEGER)",

"create unique index " +

"EVTMEM_IDX on EVTMEMS(MEM_ID, EVT_ID)",

// Créer la table LOCATIONS:

"drop table LOCATIONS",

"create table LOCATIONS " +

"(LOC_ID INTEGER primary key, " +

"LOC_NAME VARCHAR(30) not null, " +

"CONTACT VARCHAR(50), " +

"ADDRESS VARCHAR(40), " +

"CITY VARCHAR(20), " +

"STATE VARCHAR(4), " +

"ZIP VARCHAR(5), " +

"PHONE CHAR(12), " +

"DIRECTIONS VARCHAR(4096))",

"create unique index " +

"NAME_IDX on LOCATIONS(LOC_NAME)",

};

} ///:~

#/TIJ_PAGE04# #TIJ_PAGE05#

Le programme qui suit utilise l'information de CIDConnect et CIDSQL pour charger le driver JDBC, se connecter à la base de données, puis créer la structure de la table conforme au diagramme ci-dessus. Pour se connecter à la base, on appelle la méthode static DriverManager.getConnection( ), en lui passant l'URL de base de données, le nom d'utilisateur et un mot de passe. On obtient en retour un objet Connection que l'on peut utiliser pour interroger et manipuler la base. La connexion établie, il suffit d'envoyer le SQL à la base, dans ce cas en balayant le tableau CIDSQL. Toutefois, au premier lancement du programme, la commande « drop table » échouera, lançant une exception, qui sera capturée, rapportée, puis ignorée. La commande « drop table » n'a d'autre but que de permettre une expérimentation plus facile : on peut modifier le code SQL définissant les tables puis exécuter à nouveau le programme, les anciennes tables étant remplacées par les nouvelles.

Dans cet exemple, il est intéressant de laisser les exceptions s'afficher sur la console :

//: c15:jdbc:CIDCreateTables.java

// Crée les tables d'une base de données pour la

// «community interests database».

import java.sql.*;

public class CIDCreateTables {

public static void main(String[ « args)

throws SQLException, ClassNotFoundException,

IllegalAccessException {

 // Charger le driver (qui s'enregistrera lui-même)

Class.forName(CIDConnect.dbDriver);

Connection c = DriverManager.getConnection(

CIDConnect.dbURL, CIDConnect.user,

CIDConnect.password);

Statement s = c.createStatement();

for(int i = 0; i < CIDSQL.sql.length; i++) {

System.out.println(CIDSQL.sql[i]);

try {

s.executeUpdate(CIDSQL.sql[i]);

} catch(SQLException sqlEx) {

System.err.println(

"Probably a 'drop table' failed");

}

}

s.close();

c.close();

}

} ///:~

Remarquons que les modifications de la base peuvent être contrôlées en changeant Strings dans la table CIDSQL, sans modifier CIDCreateTables.

La méthode executeUpdate( ) renvoie généralement le nombre d'enregistrements affectés par l'instruction SQL. Elle est très souvent utilisée pour exécuter des instructions INSERT, UPDATE, ou DELETEmodifiant une ou plusieurs lignes. Pour les instructions telles que CREATE TABLE, DROP TABLE, et CREATE INDEX, executeUpdate( )renvoie toujours zéro.

Pour tester la base, celle-ci est chargée avec quelques données exemples. Ceci est réalisé au moyen d'une série d'INSERTface="Georgia">suivie d'un SELECTafin de produire le jeu de données. Pour effectuer facilement des additions et des modifications aux données de test, ce dernier est construit comme un tableau d'Objectà deux dimensions, et la méthode executeInsert( ) peut alors utiliser l'information d'une ligne de la table pour construire la commande SQL appropriée.

//: c15:jdbc:LoadDB.java

// Charge et teste la base de données.

import java.sql.*;

class TestSet {

Object[][ « data = {

{ "MEMBERS", new Integer(1),

"dbartlett", "Bartlett", "David",

"123 Mockingbird Lane",

"Gettysburg", "PA", "19312",

"123.456.7890","bart@you.net" },

{ "MEMBERS", new Integer(2),

"beckel", "Eckel", "Bruce",

"123 Over Rainbow Lane",

"Crested Butte", "CO", "81224",

"123.456.7890", "beckel@you.net" },

{ "MEMBERS", new Integer(3),

"rcastaneda", "Castaneda", "Robert",

"123 Downunder Lane",

"Sydney", "NSW", "12345",

"123.456.7890", "rcastaneda@you.net" },

{ "LOCATIONS", new Integer(1),

"Center for Arts",

"Betty Wright", "123 Elk Ave.",

"Crested Butte", "CO", "81224",

"123.456.7890",

"Go this way then that." },

{ "LOCATIONS", new Integer(2),

"Witts End Conference Center",

"John Wittig", "123 Music Drive",

"Zoneville", "PA", "19123",

"123.456.7890",

"Go that way then this." },

{ "EVENTS", new Integer(1),

"Project Management Myths",

"Software Development",

new Integer(1), new Float(2.50),

"2000-07-17 19:30:00" },

{ "EVENTS", new Integer(2),

"Life of the Crested Dog",

"Archeology",

new Integer(2), new Float(0.00),

"2000-07-19 19:00:00" },

// Met en relation personnes et événements

{"EVTMEMS",

new Integer(1),// Dave est mis en relation avec

new Integer(1),// l'événement Software.

new Integer(0) },

{ "EVTMEMS",

new Integer(2),// Bruce est mis en relation avec

new Integer(2),// l'événement Archeology.

new Integer(0) },

{ "EVTMEMS",

new Integer(3),// Robert est mis en relation avec

new Integer(1),// l'événement Software...

new Integer(1) },

{ "EVTMEMS",

new Integer(3), // ... et

new Integer(2), // l'événement Archeology.

new Integer(1) },

};

// Utiliser les données par défaut:

public TestSet() {}

// Utiliser un autre ensemble de données:

public TestSet(Object[][ « dat) { data = dat; }

}

public class LoadDB {

Statement statement;

Connection connection;

 TestSet tset;

public LoadDB(TestSet t) throws SQLException {

tset = t;

try {

// Charger le driver (qui s'enregistrera lui-même)

Class.forName(CIDConnect.dbDriver);

} catch(java.lang.ClassNotFoundException e) {

e.printStackTrace(System.err);

}

connection = DriverManager.getConnection(

CIDConnect.dbURL, CIDConnect.user,

CIDConnect.password);

statement = connection.createStatement();

}

public void cleanup() throws SQLException {

statement.close();

connection.close();

}

public void executeInsert(Object[ « data) {

String sql = "insert into "

+ data[0 « + " values(";

for(int i = 1; i < data.length; i++) {

if(data[i « instanceof String)

sql += "'" + data[i « + "'";

else

sql += data[i];

if(i < data.length - 1)

sql += ", ";

}

sql += ')';

System.out.println(sql);

try {

statement.executeUpdate(sql);

} catch(SQLException sqlEx) {

System.err.println("Insert failed.");

while (sqlEx != null) {

System.err.println(sqlEx.toString());

sqlEx = sqlEx.getNextException();

}

}

}

public void load() {

for(int i = 0; i< tset.data.length; i++)

executeInsert(tset.data[i]);

}

// Lever l'exception en l'envoyant vers la console:

public static void main(String[ « args)

throws SQLException {

LoadDB db = new LoadDB(new TestSet());

db.load();

try {

// Obtenir un ResultSet de la base chargée:

ResultSet rs = db.statement.executeQuery(

"select " +

"e.EVT_TITLE, m.MEM_LNAME, m.MEM_FNAME "+

"from EVENTS e, MEMBERS m, EVTMEMS em " +

"where em.EVT_ID = 2 " +

"and e.EVT_ID = em.EVT_ID " +

"and m.MEM_ID = em.MEM_ID");

while (rs.next())

System.out.println(

rs.getString(1) + "" +

rs.getString(2) + ", " +

rs.getString(3));

} finally {

db.cleanup();

}

}

} ///:~

La classe TestSet contient un ensemble de données par défaut qui est mis en oeuvre lorsqu'on appelle le constructeur par défaut ; toutefois, il est possible de créer au moyen du deuxième constructeur un objet TestSet utilisant un deuxième ensemble de données. L'ensemble de données est contenu dans un tableau à deux dimensions de type Object car il peut contenir n'importe quel type, y compris String ou des types numériques. La méthode executeInsert( ) utilise RTTI pour différencier les données String (qui doivent être entre guillemets) et les données non-String en construisant la commande SQL à partir des données. Après avoir affiché cette commande sur la console, executeUpdate( ) l'envoie à la base de données.

Le constructeur de LoadDB établit la connexion, et load( )parcourt les données en appelant executeInsert( )pour chaque enregistrement. Cleanup( )termine l'instruction et la connexion ; tout ceci est placé dans une clause finally afin d'en garantir l'appel.

Une fois la base chargée, une instruction executeQuery( )produit un ensemble résultat. La requête concernant plusieurs tables, nous avons bien un exemple de base de données relationnelle.

On trouvera d'autres informations sur JDBC dans les documents électroniques livrés avec la distribution Java de Sun. Pour en savoir plus, consulter le livre JDBC Database Access with Java (Hamilton, Cattel, and Fisher, Addison-Wesley, 1997). D'autres livres à propos de JDBC sortent régulièrement.

Les Servlets

Les accès clients sur l'Internet ou les intranets d'entreprise représentent un moyen sûr de permettre à beaucoup d'utilisateurs d'accéder facilement aux données et ressources [74]. Ce type d'accès est basé sur des clients utilisant les standards du World Wide Web Hypertext Markup Language (HTML) et Hypertext Transfer Protocol (HTTP). L'API Servlet fournit une abstraction pour un ensemble de solutions communes en réponse aux requêtes HTTP.

Traditionnellement, la solution permettant à un client Internet de mettre à jour une base de données est de créer une page HTML contenant des champs texte et un bouton « soumission ». L'utilisateur frappe l'information requise dans les champs texte puis clique sur le bouton « soumission ». Les données sont alors soumises au moyen d'une URL qui indique au serveur ce qu'il doit en faire en lui indiquant l'emplacement d'un programme Common Gateway Interface (CGI) lancé par le serveur, qui prend ces données en argument. Le programme CGI est généralement écrit en Perl, Python, C, C++, ou n'importe quel langage capable de lire sur l'entrée standard et d'écrire sur la sortie standard. Le rôle du serveur Web s'arrête là : le programme CGI est appelé, et des flux standard (ou, optionnellement pour l'entrée, une variable d'environnement) sont utilisés pour l'entrée et la sortie. Le programme CGI est responsable de toute la suite. Il commence par examiner les données et voir si leur format est correct. Si ce n'est pas le cas, le programme CGI doit fournir une page HTML décrivant le problème ; cette page est prise en compte par le serveur Web (via la sortie standard du programme CGI), qui la renvoie à l'utilisateur. Habituellement, l'utilisateur revient à la page précédente et fait une nouvelle tentative. Si les données sont correctes, le programme CGI traite les données de la manière appropriée, par exemple en les ajoutant à une base de données. Il élabore ensuite une page HTML appropriée que le serveur Web enverra à l'utilisateur.

Afin d'avoir une solution basée entièrement sur Java, l'idéal serait d'avoir côté client une applet qui validerait et enverrait les données, et côté serveur une servlet qui les recevrait et les traiterait. Malheureusement, bien que les applets forment une technologie éprouvée et bien supportée, leur utilisation sur le Web s'est révélée problématique car on ne peut être certain de la disponibilité d'une version particulière de Java sur le navigateur Web du client ; en fait, on ne peut même pas être certain que le navigateur Web supporte Java ! Dans un intranet, on peut exiger qu'un support donné soit disponible, ce qui apporte une certaine flexibilité à ce qu'on peut faire, mais sur le Web l'approche la plus sûre est d'effectuer tout le traitement du côté serveur puis de délivrer une page HTML au client. De cette manière, aucun client ne se verra refuser l'utilisation de votre site simplement parce qu'il ne dispose pas dans sa configuration du software approprié.

Parce que les servlets fournissent une excellente solution pour le support de programmation côté serveur, ils représentent l'une des raisons les plus populaires pour passer à Java. Non seulement ils fournissent un cadre pour remplacer la programmation CGI (et éliminer nombre de problèmes CGI épineux), mais tout le code gagne en portabilité inter plate-forme en utilisant Java, et l'on a accès à toutes les API Java (exceptées, bien entendu, celles qui fournissent des GUI, comme Swing).

Le servlet de base

L'architecture de l'API servlet est celle d'un fournisseur de services classique comportant une méthode service( ) appartenant au software conteneur de la servlet, chargée de recevoir toutes les requêtes client, et les méthodes liées au cycle de vie, init( )et destroy( ), qui sont appelées seulement lorsque la servlet est chargée ou déchargée (ce qui arrive rarement).

public interface Servlet {

public void init(ServletConfig config) throws ServletException;

public ServletConfig getServletConfig(); public void service(ServletRequest req, ServletResponse res)

throws ServletException, IOException; public String getServletInfo();

public void destroy();

La raison d'être de getServletConfig( )est de renvoyer un objet ServletConfig contenant l'initialisation et les paramètres de départ de cette servlet. La méthode getServletInfo( )renvoie une chaîne contenant des informations à propos de la servlet, telles que le nom de l'auteur, la version, et le copyright.

La classe GenericServlet est une implémentation de cette interface et n'est généralement pas utilisée. La classe HttpServlet est une extension de GenericServlet, elle est explicitement conçue pour traiter le protocole HTTP. C'est cette classe, HttpServlet, que vous utiliserez la plupart du temps.

Les attributs les plus commodes de l'API servlet sont les objets auxiliaires fournis par la classe HttpServlet. En regardant la méthode service( )de l'interface Servlet, on constate qu'elle a deux paramètres : ServletRequest et ServletResponse. Dans la classe HttpServlet, deux objets sont développés pour HTTP : HttpServletRequest and HttpServletResponse. Voici un exemple simple montrant l'utilisation de HttpServletResponse :

//: c15:servlets:ServletsRule.java

import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

public class ServletsRule extends HttpServlet {

int i = 0; // «persistance» de Servlet

public void service(HttpServletRequest req,

HttpServletResponse res) throws IOException {

res.setContentType("text/html");

PrintWriter out = res.getWriter();

out.print("");

out.print("A server-side strategy");

out.print("");

out.print("

Servlets Rule! " + i++);

out.print("");

out.close();

1 ///:~

La classe ServletsRule est la chose la plus simple que peut recevoir une servlet. La servlet est initialisée au démarrage en appelant sa méthode init( ), en chargeant la servlet après que le conteneur de la servlet soit chargé. Lorsqu'un client envoie une requête à une URL qui semble reliée à une servlet, le conteneur de servlet intercepte cette demande et effectue un appel de la méthode service( ), après avoir créé les objets HttpServletRequest et HttpServletResponse.

La principale responsabilité de la méthode service( )est d'interagir avec la requête HTTP envoyée par le client, et de construire une réponse HTTP basée sur les attributs contenus dans la demande. La méthode ServletsRule se contente de manipuler l'objet réponse sans chercher à savoir ce que voulait le client.

Après avoir mis en place le type du contenu de la réponse (ce qui doit toujours être fait avant d'initialiser Writer ou OutputStream), la méthode getWriter( ) de l'objet réponse renvoie un objet PrintWriter, utilisé pour écrire les données en retour sous forme de caractères (de manière similaire, getOutputStream( )fournit un OutputStream, utilisé pour les réponses binaires, uniquement dans des solutions plus spécifiques).

Le reste du programme se contente d'envoyer une page HTML au client (on suppose que le lecteur comprend le langage HTML, qui n'est pas décrit ici) sous la forme d'une séquence de Strings. Toutefois, il faut remarquer l'inclusion du « compteur de passages » représenté par la variable i. Il est automatiquement converti en String dans l'instruction print( ).

En lançant le programme, on peut remarquer que la valeur de i ne change pas entre les requêtes vers la servlet. C'est une propriété essentielle des servlets : tant qu'il n'existe qu'une servlet d'une classe particulière chargée dans le conteneur, et jamais déchargée (sauf en cas de fin du conteneur de servlet, ce qui ne se produit normalement que si l'on reboote l'ordinateur serveur), tous les champs de cette classe servlet sont des objets persistants ! Cela signifie que vous pouvez sans effort supplémentaire garder des valeurs entre les requêtes à la servlet, alors qu'avec CGI vous auriez dû écrire ces valeurs sur disque afin de les préserver, ce qui aurait demandé du temps supplémentaire et fini par déboucher sur une solution qui n'aurait pas été inter-plate-forme.

Bien entendu, le serveur Web ainsi que le conteneur de servlet doivent de temps en temps être rebootés pour des raisons de maintenance ou après une coupure de courant. Pour éviter de perdre toute information persistante, les méthodes de servlet init( ) et destroy( ) sont appelées automatiquement chaque fois que la servlet est chargée ou déchargée, ce qui nous donne l'opportunité de sauver des données lors d'un arrêt, puis de les restaurer après que la machine ait été rebootée. Le conteneur de la servlet appelle la méthode destroy( ) lorsqu'il se termine lui-même, et on a donc toujours une opportunité de sauver des données essentielles pour peu que la machine serveur soit intelligemment configurée.

#/TIJ_PAGE05# #TIJ_PAGE06#

L'utilisation de HttpServletpose une autre question. Cette classe fournit les méthodes doGet( ) et doPost( )qui différencient une soumission « GET » CGI de la part du client, et un « POST »CGI. « GET et POST se différencient uniquement par les détails de la manière dont ils soumettent les données, ce qui est une chose que personnellement je préfère ignorer. Toutefois, la plupart des informations publiées que j'ai pu voir semblent recommander la création de méthodes doGet( )et doPost( ) séparées plutôt qu'une seule méthode générique service( )qui traiterait les deux cas. Ce favoritisme semble faire l'unanimité, mais je ne l'ai jamais entendu expliquer d'une manière qui me laisserait supposer qu'il s'agit d'autre chose que de l'inertie des programmeurs CGI habitués à porter leur attention sur le fait qu'on doit utiliser un GET ou un POST. Aussi, dans l'esprit de faire les choses les plus simples qui fonctionnent ][75 « "text-decoration: none">,j'utiliserai uniquement la méthode service( ) pour ces exemples, et laisserai au lecteur le soin de choisir entre les GETs et les POSTs. Gardez toutefois à l'esprit qu'il aurait pu m'arriver d'oublier quelque chose et que cette simple dernière remarque pourrait être une excellente raison d'utiliser de préférence les méthodes doGet( ) et doPost( ).

Quand un formulaire est soumis à un servlet, HttpServletRequest est préchargée avec les données du formulaire présentées sous la forme de paires clef/valeur. Si on connaît le nom des champs, il suffit d'y accéder directement avec la méthode getParameter( ) pour connaître leur valeur. Il est également possible d'obtenir un objet Enumeration (l'ancienne forme d'un Iterator) vers les noms des champs, ainsi que le montre l'exemple qui suit. Cet exemple montre aussi comment un seul servlet peut être utilisé pour produire à la fois la page contenant le formulaire et la réponse à cette page (on verra plus tard une meilleure solution utilisant les JSP). Si Enumeration est vide, c'est qu'il n'y a plus de champs ; cela signifie qu'aucun formulaire n'a été soumis. Dans ce cas, le formulaire est élaboré, et le bouton de soumission rappellera la même servlet. Toutefois les champs sont affichés lorsqu'ils existent.

//: c15:servlets:EchoForm.java

// Affiche les couples nom-valeur d'un formulaire HTML

import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

import java.util.*;

public class EchoForm extends HttpServlet {

public void service(HttpServletRequest req,

HttpServletResponse res) throws IOException {

res.setContentType("text/html");

PrintWriter out = res.getWriter();

 Enumeration flds = req.getParameterNames();

if(!flds.hasMoreElements()) {

// Pas de formulaire soumis -- on en crée un:

out.print("");

out.print("

" action=\"EchoForm\">");

for(int i = 0; i < 10; i++)

out.print("Field" + i + " " +

"

" size=\"20\" name=\"Field" + i +

"\" value=\"Value" + i + "\">
");

out.print("

" Value=\"Submit\">");

} else {

out.print("

Your form contained:

");

while(flds.hasMoreElements()) {

String field= (String)flds.nextElement();

String value= req.getParameter(field);

out.print(field + " = " + value+ "
");

}

}

out.close();

}

} ///:~

On peut penser en lisant cela que Java ne semble pas conçu pour traiter des chaînes de caractères car le formatage des pages à renvoyer est pénible à cause des retours à la ligne, des séquences escape, et du signe + inévitable dans la construction des objets String. Il n'est pas raisonnable de coder une page HTML quelque peu conséquente en Java. Une des solutions est de préparer la page en tant que fichier texte séparé, puis de l'ouvrir et de la passer au serveur Web. S'il fallait de plus effectuer des substitutions de chaînes dans le contenu de la page, ce n'est guère mieux car le traitement des chaînes en Java est très pauvre. Si vous rencontrez un de ces cas, il serait préférable d'adopter une solution mieux appropriée (mon choix irait vers Python ; voici une version incluse dans un programme Java appelé JPython) qui génère une page-réponse.

Les Servlets et le multithreading

Le conteneur de servlet dispose d'un ensemble de threads qu'il peut lancer pour traiter les demandes des clients. On peut imaginer cela comme si deux clients arrivant au même moment étaient traités simultanément par la méthode service( ). En conséquence la méthode service( ) doit être écrite d'une manière sécurisée dans un contexte de thread. Tous les accès aux ressouces communes (fichiers, bases de données) demandent à être protégés par le mot clef synchronized.

L'exemple très simple qui suit utilise une clause synchronized autour de la méthode sleep( )du thread. En conséquence les autres threads seront bloqués jusqu'à ce que le temps imparti (cinq secondes) soit écoulé. Pour tester cela il faut lancer plusieurs instances d'un navigateur puis lancer ce servlet aussi vite que possible ; remarquez alors que chacun d'eux doit attendre avant de voir le jour.

//: c15:servlets:ThreadServlet.java

import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

public class ThreadServlet extends HttpServlet {

int i;

public void service(HttpServletRequest req,

HttpServletResponse res) throws IOException {

res.setContentType("text/html");

PrintWriter out = res.getWriter();

synchronized(this) {

try {

Thread.currentThread().sleep(5000);

} catch(InterruptedException e) {

System.err.println("Interrupted");

}

}

out.print("

Finished " + i++ + "

");

out.close();

}

} ///:~

On peut aussi synchroniser complètement la servlet en mettant le mot clef synchronized juste avant la méthode service( ). En réalité, l'unique justification pour utiliser la clause synchronized à la place de cela est lorsque la section critique se trouve dans un chemin d'exécution qui ne doit pas être exécuté. Dans un tel cas, il serait préférable d'éviter la contrainte de synchronisation à chaque fois en utilisant une clause synchronized. Sinon, chaque thread particulier devrait systématiquement attendre, il vaut donc mieux synchroniser la méthode en entier.

Gérer des sessions avec les servlets

HTTP est un protocole qui ne possède pas la notion de session, on ne peut donc savoir d'un appel serveur à un autre s'il s'agit du même appelant ou s'il s'agit d'une personne complètement différente. Beaucoup d'efforts ont été faits pour créer des mécanismes permettant aux développeurs Web de suivre les sessions. À titre d'exemple, les compagnies ne pourraient pas faire de e-commerce si elles ne gardaient pas la trace d'un client, ainsi que les renseignements qu'il a saisi sur sa liste de courses.

Il existe plusieur méthodes pour suivre une session, mais la plus commune utilise les » cookies persistants, « qui font intégralement partie du standard Internet. Le

 HTTP Working Group de l'Internet Engineering Task Force a décrit les cookies du standard officiel dans RFC 2109 (ds.internic.net/rfc/rfc2109.txt ou voir www.cookiecentral.com).

Un cookie n'est pas autre chose qu'une information de petite taille envoyée par un serveur Web à un navigateur. Le navigateur sauvegarde ce cookie sur le disque local, puis lors de chaque appel à l'URL associée au cookie, ce dernier est envoyé de manière transparente en même temps que l'appel, fournissant ainsi au serveur l'information désirée en retour (en lui fournissant généralement d'une certaine manière votre identité). Toutefois les clients peuvent inhiber la capacité de leur navigateur à accepter les cookies. Si votre site doit suivre un client qui a inhibé cette possibilité, alors une autre méthode de suivi de session doit être intégrée à la main (réécriture d'URL ou champs cachés dans un formulaire), car les fonctionnalités de suivi de session intégrées à l'API servlet sont construites autour des cookies.

La classe Cookie

L'API servlet (à partir de la version 2.0) fournit la classe Cookie. Cette classe inclut tous les détails de l'en-tête HTTP et permet de définir différents attributs de cookie. L'utilisation d'un cookie consiste simplement à l'ajouter à l'objet réponse. Le constructeur a deux arguments, le premier est un nom du cookie et le deuxième une valeur. Les cookies sont ajoutés à l'objet réponse avant que l'envoi ne soit effectif.

Cookie oreo = new Cookie("TIJava", "2000"); res.addCookie(cookie);

Les cookies sont récupérés en appelant la méthode getCookies( ) de l'objet HttpServletRequest, qui renvoie un tableau d'objets Cookie.

Cookie[ « cookies = req.getCookies();

En appelant getValue( )pour chaque cookie, on obtient une String initialisée avec le contenu du cookie. Dans l'exemple ci-dessus, getValue("TIJava")renverrait une String contenant » 2000.]

La classe Session

Une session consiste en une ou plusieurs requêtes de pages adressées par un client à un site Web durant une période définie. Par exemple, si vous faites vos courses en ligne, la session sera la période démarrant au moment où vous ajoutez un achat dans votre panier jusqu'au moment où vous envoyez effectivement la demande. Chaque achat ajouté au panier déclenchera une nouvelle connexion HTTP, qui n'a aucun rapport ni avec les connexions précédentes ni avec les achats déjà inclus dans votre panier. Pour compenser ce manque d'information, les mécanismes fournis par la spécification des cookies permet au servlet de suivre la session.

Un objet servlet Session réside du côté serveur sur le canal de communication ; son rôle est de capturer les données utiles à propos du client pendant qu'il navigue sur votre site Web et qu'il interagit avec lui. Ces données peuvent être pertinentes pour la session actuelle, comme les achats dans le panier, ou bien peuvent être des information d'authentification fournies lors de l'accès du client au site Web, et qu'il n'y a pas lieu de donner à nouveau durant un ensemble particulier de transactions.

La classe Session de l'API servlet utilise la classe Cookie pour effectuer ce travail. Toutefois, l'objet Session n'a besoin que d'une sorte d'identifiant unique stocké chez le client et passé au serveur. Les sites Web peuvent aussi utiliser les autres systèmes de suivi de session mais ces mécanismes sont plus difficiles à mettre en oeuvre car ils n'existent pas dans l'API servlet (ce qui signifie qu'on doit les écrire à la main pour traiter le cas où le client n'accepte pas les cookies).

Voici un exemple implémentant le suivi de session au moyen de l'API servlet :

//: c15:servlets:SessionPeek.java

// Utilise la classe HttpSession.

import java.io.*;

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;

3