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","[email protected]" },
{ "MEMBERS", new Integer(2),
"beckel", "Eckel", "Bruce",
"123 Over Rainbow Lane",
"Crested Butte", "CO", "81224",
"123.456.7890", " [email protected]" },
{ "MEMBERS", new Integer(3),
"rcastaneda", "Castaneda", "Robert",
"123 Downunder Lane",
"Sydney", "NSW", "12345",
"123.456.7890", " [email protected]" },
{ "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("
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("
");
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("
");
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.*;
...
Les classes JDK les plus utilisées
package description
Java.applet Contient la classe Applet et 3 interfaces. Elles servent d’intermédiaires entre une applet et le navigateur.
Java.awt Interface utilisateur : Egalement chargement de graphiques, impression, distribution d’éléments graphiques...
Java.awt.datatransfe r Support du presse-papier (copier/coller...)
Java.awt.event Gestionnaire d’évènements AWT (clic de souris ...
Java.awt.image Gestion des images
Java.io Gestion des entrées/sorties (écriture et lecture de données)
Java.lang Contient la classe racine Object, types de données avancés... importée par défaut
Java.math Fonctions mathématiques
Java.net
Client/serveur
Java.rmi Modules RMI (Remote Method Invocation) pour invoquer des méthodes à distance
Java.security Fonctions de sécurisation des transmission
Java.sql L’accès aux bases de données relationnelles par l’intermédiaire du langage SQL
Java.text Formatage des chaînes de texte
Java.util Conversions, structures de données, gestion d’évènements
Javax.swing Tous les composants de l’interface utilisateur SWING
Les types élémentaires (les types traitant les bits et les octets ont été omis).
Type élémentaire intervalle de variation nombre de bits
boolean false , true 1 bit
byte [-128 , +127 ] 8 bits
char caractères unicode (valeurs de 0 à 65536) 16 bits
double Virgule flottante double précision ~5.10308 64 bits
float Virgule flottante simple précision ~9.10 18 32 bits
int entier signé : [-231, +231 - 1] 32 bits
long entier signé long : [-263, +263- 1] 64 bits
short entier signé court : [-215, +215 -1] 16 bits
Transtypage (cast): opérateur ( ) : Il s'agit de la conversion d'un type à un autre. Exemple
int x ;
x = (int) y ;
Transtyper la valeur contenue dans la variable y en un entier signé 32 bits avant de la mettre dans la variable x.
declaration de variables. Exemples
int Bonjour ; float Valeur ; char UnCar ; boolean Test ; Affectation : Le symbole d'affectation en Java est " = " :
Affectation simple Affectation lors de la déclaration (initialisation) Exemple avec cast :
Bonjour = 2587 ; Valeur = -123.5687 ; UnCar = 'K' ;
int Bonjour = 2587 ;
float Valeur = -123.5687 ; char UnCar = 'K' ; int Valeur ; char car = '8' ;
Valeur = (int)car - (int)'0';
L'affectation peut être utilisée dans une expression :
a = (b = 12)+8 ; // b prend une nouvelle valeur dans l'expression avant l'affectation a = b = c = d =8 ; // affectation multiple
Autre forme d'affectation
x op= y ; signifie x = x op y;
Exemples:
int a , b = 56 ; a = -8 ;
a += b ; // équivalent à : a = a + b
b *= 3 ; // équivalent à : b = b * 3
Les structures de contrôle. La structure if_else
int a , b , c ;
if ( b = = 0 ) c =1 ;
else {
c = a / b;
System.out.println("c = " + c); } System.out.println("c = " + c);
est un appel de la méthode println de la classe out du package java System, elle écrit sur la sortie standard (console) (voir tp1)
if ((c = a*b) != 0) c += b; else c = a; L'instruction " if ((c = a*b) != 0) c +=b; else c = a; " contient une affectation intégrée dans le test : la valeur de a*b est rangée dans c avant d'effectuer le test sur c.
L'operateur conditionnel. (appelé aussi operateur ternaire) variable = condition ? expression1:expression2;
Exemple : utiliser l'opérateur conditionnel pour afecter à c le plus grand de deux entiers a et b.
int a , b , c ; c = a>b ? a : b ;
Le switch
int x = 10; Tous les tests sont effectués, meme après rencontre d'un
switch (x+1) cas ou la condition est vraie
{ case 11 : System.out.println(" 11");
case 12 : System.out.println(" 12");
default : System.out.println(" default");
}
Dans ce cas le déroulement de l'instruction switch après
char x ; déroutement vers le bon case, est
switch (x) interrompu par le break qui renvoie la suite de
{ l'exécution après la fin du bloc switch.
case 'a' : InstrA ; break;
case 'b' : InstrB ; break;
default : InstrElse; }
La boucle for
for ( i = 1; i<=10; i++ )
{
InstrA ;
InstrB ; ...
}
Les deux formes de la boucle while
while ( Expr ) // Expr est évaluée avant la première itération
{
InstrA ;
InstrB ; ...
}
do
{
InstrA ;
InstrB ; ...
} while ( Expr ) // évaluation après la première itération même fausse la boucle est exécutée au moins une fois (équivalent du repeat until du pascal).
L'instruction continue
Une instruction continue ne peut se situer qu'à l'intérieur du corps d'instruction de l'une des trois itérations while, do..while, for. Lorsque continue est présente elle interrompt l'exécution de la séquence des instructions situées après elle, et force le rebouclage de la boucle (itération suivante)
La classe String
Le type de données String (chaîne de caractère) n'est pas un type élémentaire en Java, c'est une classe. Donc une chaîne de type String est un objet qui n'est utilisable qu'à travers les méthodes de la classe String.
Pour accéder à la classe String et à toutes ses méthodes, vous devez mettre avant la déclaration de la classe l'instruction d'importation de package suivante :
import java.lang.String ;
Un littéral de chaîne est une suite de caractères entre guillemets : " abcdef "
Etant donné que cette classe est très utilisée les variables de type String bénéficient d'un statut d'utilisation aussi souple que celui des autres types élémentaires. On peut les considérer comme des listes de caractères numérotés de 0 à n-1 (si n est le nombre de caractères de la chaîne).
Déclaration d'une variable String : String str1;
Déclaration d'une variable String avec initialisation :
String str1 = " abcdef "; ou String str1 = new String("abcdef ");
On accède à la longueur d'une chaîne par la méthode : int length( ) String str1 = "abcdef";
int longueur;
longueur = str1.length( ); // ici longueur vaut 5
Toutefois les String de Java sont moins conviviales en utilisation que les string de pascal ou
celles de C#, il appartient au programmeur d'écrire lui-même ses méthodes d'insertion, modification et suppression.
Le type String possède des méthodes classiques d'extraction, de concaténation, de changement de casse, etc.
Concaténation de deux chaînes : Opérateur : + sur les chaînes ou Méthode : String s1 concat(String s2) Les deux écritures ci-dessous sont donc équivalentes en Java :
str3 = str1+str2 ou str3 = str1.concat(str2)
On accède à un caractère de rang fixé d'une chaîne par la méthode : charAt(int rang)
String ch1 = "abcdefghijk";
char car = ch1.charAt(4); // ici la variable car contient la lettre 'e'
position d'une sous-chaîne à l'intérieur d'une chaîne donnée : méthode : int indexOf ( String ssch) String ch1 = " abcdef " , ssch="cde";
int rang ;
rang = ch1.indexOf ( ssch ); // ici la variable rang vaut 2
Opérateurs d'égalité de String
La méthode boolean equals(Object s) teste si deux chaînes ont la même valeur.
String a , b ;
a.equals ( b ) renvoie true si les variables a et b ont la même valeur sinon il renvoie false.
Attention !!:
char car = 'r'; String s;
s = String.valueOf(car);
String s1 , s2 ="abc" ; char c = 'e' ; s1 = s2 + 'd' ; s1 = s2 + c ;
Les tableaux à une dimension Déclaration d'un tableau :
int [ ] table1; char [ ] table2; float [ ] table3; String [ ] tableStr;
Déclaration d'un tableau avec définition explicite de taille :
int [ ] table1 = new int [5]; char [] table2 = new char [12]; float [ ] table3 = new float [8]; String [ ] tableStr = new String [9];
Le mot clef new correspond à la création d'un nouvel objet (un nouveau tableau) dont la taille est fixée par la valeur indiquée entre les crochets. Ici 4 tableaux sont créés et prêts à être utilisés : table1 contiendra 5 entiers 32 bits, table2 contiendra 12 caractères, table3 contiendra 8 réels en simple précision et tableStr contiendra 9 chaînes de type String.
Déclaration et initialisation d'un tableau avec définition implicite de taille :
int [ ] table1 = {17,-9,4,3,57};
char [ ] table2 = {'a','j','k','m','z'};
float [ ] table3 = {-15.7f,75,-22.03f,3,57};
String [ ] tableStr = {"chat","chien","souris","rat","vache"};
Utiliser un tableau
Un tableau en Java comme dans les autres langages algorithmiques, s'utilise à travers une cellule de ce tableau repérée par un indice obligatoirement de type entier ou un char considéré comme un entier (byte, short, int, long ou char). Le premier élément d'un tableau est numéroté 0, le dernier length-1. On peut ranger des valeurs ou des expressions du type général du tableau dans une cellule du tableau.
Exemple avec un tableau de type int :
int [ ] table1 = new int [5];
// dans une instruction d'affectation:
table1[0] = -458; table1[4] = 5891;
table1[5] = 72;
// dans une instruction de boucle:
for (int i = 0 ; i<= table1.length-1; i++)
table1[i] = 3*i-1; // après la boucle: table1 = {-1,2,5,8,11}
Exemple d'écritures conseillées de matrice de type int :
int [ ][ ] table1 = new int [2][3];// deux lignes de dimension 3 chacune // dans une instruction d'affectation:
table1[0][0] = -458;
table1[2][5] = -3;
for (int i = 0 ; i<= 2; i++) table1[1][i] = 3*i-1;
// dans une instruction de boucles imbriquées:
for (int i = 0 ; i<= 2; i++)
for (int k= 0 ; i<= 3; i++) table1[i][k] = 100;
III. le modèle objet de java. Les classes
Tous les programmes Java sont composés d'au moins une classe (une application qui ne contient qu'une classe est dite 'autonome' (standalone). Les classes sont déclarées avec le mot clé class, le code est renfermé entre deux accolades. Une classe comporte un ensemble de variables (propriétés) et un ensemble de méthodes.
Une méthode contient :
Une méthode peut avoir une valeur de retour : le résultat que la méthode va retourner. Elle est dans ce cas similaire à une fonction. Les méthodes de type void ne renvoient rien elles sont similaires à des procédures.
exemple 1
public double arrondi(double A, int B) {
return (double) ( (int) (A * Math.pow(10, B) + .5)) / Math.pow(10, B); }
C'est une méthode nommée arrondi de type double (une fonction qui rends une valeur de type double), c'est une méthode publique (voir plus loin), elle utilise la methode pow (puissance) de la classe java prédéfinie Math.
exemple 2
public void bonjour() { sytem.out.println ("Bonjour tout le monde !!"); }
c'est une méthode nommée bonjour qui ne rend aucune valeur (donc une procédure) et affiche la phrase entre guillemets .
Remarques
le mot clé public est un modificateur de visiblité , il en existe d'autres pour les classes, les méthodes et les variables (abstract, private). Pour un débutant public et private suffisent
Exemple de classe
public class Personne {
String nom; String prenom; int age;
// Constructeur par défaut
public Personne(){ nom = "Inconnu";
prenom = "Inconnu"; age = 0;
}
// constructeur d'initialisation
public Personne(String N, int A, String P) {
nom = N;
prenom = P;
age = A;
}
public String getNom()// Retourne le nom
{
return nom;
}
public String getprenom() // retourne le prénom
{
return prenom;
}
public int getage() // Retourne l'age
{
return age ;
}
public void setNom(String nm) // Définit le nom
{
nom = nm;
}
public void set prenom(String pr) // Définit le prénom
{
prenom = pr;
}
public void setage (int ag) // Définit l'age
{
Age = ag;
}
}
}
Dans le code // et /* délimitent des commentaires (respectivement uniligne et multilignes): Les variables si elles sont publiques peuvent être accédées directement comme suit: nom_instance.nom_variable
Pour respecter l'encapsulation il serait mieux de les déclarer private et de les accéder par des setters et des getters.
Exemple : private String nom; Opérateurs this et super
Exprimer l'heritage : le mot clé extends
Class1 extends Class2
L'heritage en java est toujours simple et jamais multiple Instancier un objet
Le mot-clé new est utilisé pour instancier un objet. Par exemple: nous avons une classe Voiture et nous voulons créer une instance une206
Nom-classe variable = new nom-classe() Voiture une206=new Voiture();
Le new crée l'objet en mémoire et exécute son constructeur.